Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky

.pdf
Скачиваний:
34
Добавлен:
22.03.2016
Размер:
2.08 Mб
Скачать

программ, где применяются объекты этого класса;

достижение защиты объекта от нежелательных и непредсказуемых взаимодействий с ним других фрагментов программ, в которых он используется.

Для реализации в классах принципа инкапсуляции используется разграничение доступа к его членам. Основной принцип состоит в следующем. Доступ к данным (к

полям) объектов должен быть возможен только через средства внешнего интерфейса, предоставляемые классом. Если не рассматривать применений классов в цепочках наследования и в сборках (всему свое время, см. главу 13), то реализацию класса определяют его закрытые члены (со спецификатором private), а

внешний интерфейс открытые (со спецификатором public). Обычная практика закрывать все поля класса и открывать только те средства (например, методы),

которых достаточно для работы с объектами класса.

Итак, поля классов рекомендуется определять как закрытые, а для обеспечения достаточно полного интерфейса вводить нужное количество открытых методов.

Полнота внешнего интерфейса определяется требованиями тех программ, которые должны работать с классом и его объектами.

Объектно-ориентированный подход к программированию традиционно рекомендует для расширения внешнего интерфейса класса и его объектов вводить в

класс специальные методы, позволяющие получать значения закрытых полей и позволяющие желаемым способом задавать их значения. По-английски эти методы называют, соответственно, get method "метод: get method" (accessor) – метод

доступа и set method "метод: set method" (mutator) – метод изменения . В

зависимости от целей решаемых задач для каждого поля могут быть заданы или оба метода, или один из них.

В качестве примера рассмотрим программу с классом, объекты которого содержат сведения о людях. Для каждого человека в закрытых полях определены его фамилия (string фамилия) и год рождения ( int год_рождения). Для поля с фамилией определим метод получения getName() и метод изменения setName()

значения. Для поля, определяющего год рождения, введем только метод получения значения getAge(). Класс с названными методами может быть таким (программа

12_01.cs):

class Person // Класс человек

{ // Закрытые поля: readonly int год_рождения; string фамилия;

public Person(string name, int year) // конструктор

{

 

фамилия = name;

 

год_рождения = year;

}

// аксессор

public string getName()

{ return фамилия; }

 

public void setName(string name) // мутатор

{ фамилия = name; }

// аксессор

public int getAge()

{ return год_рождения; }

}

 

В классе определен конструктор общего вида, позволяющий при создании

объекта указать фамилию и год рождения человека. Обратите внимание, что для поля год_рождения использован модификатор readonly. В классе нет метода,

позволяющего изменить значение поля

год_рождения после создания объекта

класса. Это соответствует смыслу поля. Следующий фрагмент программы

иллюстрирует применение методов класса person (см. программу 12_01.cs). static void Main()

{

Person one = new Person("Кулик", 1976);

Console.WriteLine("Фамилия: {0}, год рождения: {1}", one.getName( ), one.getAge( )); Console.Write("Введите новую фамилию: ");

string name = Console.ReadLine( ); one.setName(name);

Console.WriteLine("Фамилия: {0}, год рождения: {1}", one.getName( ), one.getAge( ));

}

Результат выполнения программы:

Фамилия: Кулик, год рождения: 1976 Введите новую фамилию: Смирнова<ENTER>

Фамилия: Смирнова, год рождения: 1976

В связи с рассмотрением доступности членов класса полезно привести

относящиеся к классам сведения по системе обозначений языка UML (Unified

Modeling Language – Унифицированный язык моделирования). В UML класс

изображают как прямоугольник, разделённый по вертикали на три части. Верхняя

содержит имя класса, средняя его атрибуты (поля, свойства), нижняя методы

класса. Для атрибутов и методов введены условные обозначения их доступности.

Дефис (–) обозначает вид доступа

private (закрытый), диез (#) – protected

(защищённый), плюс (+) – public (открытый).

В Visual Studio используется основанная на UML, но немного

модифицированная система графического представления классов. В качестве

примера рассмотрим (см. рис. 12.1) изображение класса Person.

Рис. 12.1. Графическое представление класса Person в Visual Studio

Как и принято в UML в верхней части изображения название класса Person и

сопровождающая надпись, объясняющая, что это класс. Следующая часть

озаглавлена Fields (Поля). В ней названия двух полей класса (

год_рождения и

фамилия), снабжённые значком, соответствующим статусу доступа

private

(закрытый). В части с заголовком Methods (методы) перечислены методы объектов

(getAge, getName, Person, setName). Каждое название снабжено значком

"открытый" (public).

Примечание: чтобы получить графическое изображение класса нужно воспользоваться средствами Visual Studio. Если проект с вашей программой загружен в Visual Studio, то откройте панель Solution Explorer. Найдите в этом окне название проекта и щёлкните на нём правой кнопкой мыши. В выпадающем меню выбирайте пункт View Сlass Diagram и активизируйте его щелчком мыши. Система (Visual Studio 2010) выполнит построение диаграммы классов, входящих в ваш проект. Обычно в проекте более одного класса и вначале они изображены в "свёрнутом" виде каждый. Развернуть нужный вам класс можно щелчком мыши в иконке , размещённой в правом верхнем углу.

12.2. Свойства классов

Кроме традиционного для объектно-ориентированных языков применения

специальных методов, обеспечивающих обращение к закрытым полям, язык C#

позволяет получить к ним доступ с помощью механизма свойств. Однако, свойство это не просто средство доступа к полям класса или его объекта. У свойств более широкие возможности. Дело в том, что в ряде случаев объекты класса могут иметь признаки вторичные по отношению к значениям их полей. Например для объектов

приведенного выше класса Person с полями фамилия и год_рождения разумно

ввести такой показатель, как возраст в текущий момент времени. Делать такой показатель полем объекта неудобно значение показателя должно зависеть не

только от времени создания объекта, но и от момента обращения к этому объекту.

Для класса чисел в научной нотации вторичными характеристиками каждого объекта можно сделать, например, его значение в естественной форме вещественного числа или значение 10 p, где р порядок числа в научной нотации,

представленный полем объекта. Если в классе треугольников полями объектов сделать длины трёх сторон треугольника, то такие характеристики как периметр или площадь можно объявить свойствами объектов класса.

Свойство "свойство" – это член класса, который обеспечивает доступ к характеристикам класса или его объекта. С точки зрения внешнего пользователя свойства синтаксически не отличаются от полей класса. Однако, между свойствами и полями имеется принципиальное различие в объекте отсутствует ассоциированный со свойством участок памяти. Такой участок памяти выделяется для каждого поля класса, а свойство, связанное с этим полем, представляет собой упрощённое по сравнению с методами средство для получения либо задания значения поля.

Для обращения к свойствам применяются их имена. Эти имена можно использовать в выражениях. Однако, прежде чем объяснять особенности

применения свойств, рассмотрим правила их объявления в классе.

Декларация свойства "декларация свойства" состоит из двух частей. Первая часть подобна объявлению поля. Вторая часть представляет собой конструкцию особого вида, включающую пару особых методов с фиксированными именами: get и set. Эти специфические методы называют аксессорами "аксессор" . Общую форму

объявления свойства "свойство: объявления свойства" можно представить так:

модификаторы_свойстваopt

тип_свойства имя_свойства

{

декларация get-аксессора opt

декларация set-аксессора opt

}

В качестве модификаторов свойства используются: new, public, protected,

internal, private, static, virtual, sealed, override, abstract, extern

В объявлении свойства могут присутствовать в допустимых сочетаниях

несколько модификаторов, которые в этом случае отделяются друг от друга пробелами. Модификаторы, определяющие доступность членов вне объявления класса, мы уже рассмотрели в связи с полями и методами классов. Модификатор static позволяет отнести свойство к классу в целом, а не к его объектам. Остальные модификаторы до изучения наследования рассматривать не будем.

Тип_свойства "свойство: тип_свойства" это тип той характеристики,

которую представляет свойство.

Имя_свойства "свойство: имя_свойства" – идентификатор, выбираемый

программистом для именования свойства.

Вторая часть объявления свойства (можно назвать её телом "свойство: тело свойства" объявления свойства) – это заключенная в фигурные скобки пара объявлений "методов" – аксессоров со специальными именами get и set.

Формат декларации аксессора доступа "декларация аксессора доступа" (get-

аксессора):

модификаторы_аксессораopt get тело_аксессора

Формат декларации аксессора изменения

"декларация аксессора

изменения" (set -аксессора):

модификаторы_аксессораopt set тело_аксессора

Модификаторы аксессоров: protected, internal, private, protected internal,

internal protected

Тело аксессора это либо блок, либо пустой оператор, обозначаемый символом точка с запятой. Пустой оператор в качестве тела применяется для аксессоров тех свойств, которые объявлены с модификаторами abstract и extern. Сейчас такие свойства мы не рассматриваем.

Аксессор доступа "аксессор доступа" (get-аксессор)подобен методу без параметров, возвращающему значение, тип которого определяется типом свойства.

Достаточно часто аксессор доступа возвращает значение конкретного поля класса или его объекта. Для возврата значения из аксессора в его теле должен выполниться оператор

return выражение;

Аксессор изменения "аксессор изменения" (set-аксессор)подобен методу с возвращаемым значением типа void и единственным неявно заданным параметром,

значение которого определяет новое значение свойства. Тип параметра определяется типом свойства. Имя параметра, которое используется в теле аксессора изменений,

всегда value.

В теле аксессоров свойства могут быть сложные алгоритмы обработки.

Например, при изменении свойства можно контролировать диапазон допустимых значений. В теле аксессора доступа возвращаемое значение может вычисляться с учетом значений не одного, а разных полей, и т.д.. Часто свойство используют для работы с одним закрытым полем класса. Заметим, что и при таком использовании

свойство не вводит новых полей, а только управляет доступом к уже существующим в классе полям. Существует соглашение (не обязательное) начинать имена свойств с заглавных букв. Если свойство представляет "во внешнем мире"

конкретное поле класса, то имя свойства повторяет имя поле, но отличается от него первой заглавной буквой. Например, если в классе объявлено поле tempor, то представляющее его свойство рекомендуется назвать Tempor.

Пример класса чисел в научной нотации со свойствами (12_02.cs): class Real // Класс чисел в научной нотации

{ // Закрытые поля:

double m = 8.0; // мантисса - явно инициализирована int p; // инициализация по умолчанию

// свойство для получения значения мантиссы: public double Mantissa

{

get { return m; }

}

//свойство для показателя: public int Exponent

{

get { return p; } set { p = value; }

}

//свойство для значения числа: public double RealValue

{

get { return m * Math.Pow(10, p); } set { m = value; p = 0;

reduce();

}

}

void reduce() // "Внутренний" для класса метод

{

double sign = 1; if (m < 0) {sign = -1; m = -m; } for (; m >= 10; m /= 10, p += 1) ;

for (; m < 1; m *= 10, p -= 1) ; m *= sign;

}

}

Вклассе Real уже рассмотренные закрытые члены: вспомогательный метод

reduce(), поля double m – мантисса, int p – показатель. Кроме того, объявлены три

открытых свойства:

public double Mantissa – для получения значения мантиссы; public int Exponent – для получения и изменения показателя;

public double RealValue – для получения числа в виде значения вещественного типа и для задания значений полей объекта по значению типа double.

В определении свойства Mantissa только один аксессор get, он позволяет получить значение поля double m.

Свойство Exponent включает два аксессора: set { p = value; } изменяет значение поля int p;

get { return p; } возвращает значение того же поля.

Свойство RealValue позволяет обратиться к объекту класса Real как к числовому значению типа double. Аксессоры свойства:

get { return m * Math.Pow(10, p); }

set { m = value; p = 0; reduce(); }

Аксессор get возвращает числовое значение, вычисленное на основе значений полей объекта.

Аксессор set, получив из внешнего обращения значение value, присваивает его переменной поля double m. При этом переменная int p получает нулевое значение.

Затем для приведения числа к научной нотации в теле аксессора выполняется обращение к вспомогательному методу класса reduce(). Его мы уже рассмотрели в

связи с обсуждением конструкторов.

Следующий фрагмент кода иллюстрирует применение свойств класса

(программа 12_02.cs): static void Main()

{

Real number = new Real(); // конструктор умолчания string form = " = {0,8:F5} * 10^{1,-3:D2}"; Console.WriteLine("number" + form,

number.Mantissa, number.Exponent); number.Exponent = 2; Console.WriteLine("number" + form,

number.Mantissa, number.Exponent); Console.WriteLine("number RealValue = " + number.RealValue);

number.RealValue = -314.159; Console.WriteLine("number" + form,

number.Mantissa, number.Exponent); Console.WriteLine("number RealValue = " + number.RealValue);

}

В программе с помощью конструктора умолчания Real() определен один объект класса чисел в научной нотации и объявлена ссылка number,

ассоциированная с этим объектом. Дальнейшие манипуляции с объектом выполнены с помощью свойств Mantissa, Exponent, RealValue. Для обращения к ним

используются уточненные имена вида имя_объекта.имя_свойства. Результат выполнения программы:

number = 8,00000 * 10^00 number = 8,00000 * 10^02 number RealValue = 800 number = -3,14159 * 10^02 number RealValue = -314,159

В первой строке результатов приведено изображение числа из объекта,

созданного конструктором умолчания. Значения полей при выводе получены с помощью уточненных имён свойств number.Mantissa, number.Exponent.

Оператор number.Exponent = 2; через свойство Exponent изменяет значение поля показателя int p. Этим определяется вторая строка результатов выполнения программы.

В третьей строке числовое значение объекта number, полученное с помощью свойства RealValue.

Оператор number.RealValue = -314.159; через свойство RealValue изменяет оба поля объекта number.

Результат изменения полей иллюстрирует предпоследняя строка результатов.

В последней строке значение свойства RealValue.

Аксессор get выполняется, когда из кода, внешнего по отношению к классу или его объекту, выполняется «чтение» значения свойства. При этом в точку вызова

возвращается некоторое значение или ссылка на объект. Тип значения или ссылки соответствует типу в объявлении свойства. При этом возможны неявные приведения типов. Например, если get-аксессор возвращает значение типа int, а тип свойства double, то будет автоматически выполнено приведение типов. Get-аксессор подобен методу без параметров, возвращающему значение или ссылку с типом свойства.

Если внешний по отношению к классу или его объекту код присваивает некоторое значение свойству, то вызывается set-аксессор этого свойства. В теле

этого аксессора присвоенное свойству значение представлено специальной переменной с фиксированным именем value. Тип этой переменной совпадает с типом свойства. У set-аксессора возвращаемое значение отсутствует. Можно считать, что set-аксессор функционально подобен методу с одним параметром. У этого параметра тот же тип, что и тип свойства, и фиксированное имя value.

Можно использовать в объявлении свойства только один из аксессоров. Это позволяет вводить свойства только для записи (изменения) и свойства только для

чтения. Возникает вопрос, чем открытое свойство, обеспечивающее только чтение,

отличается от открытого поля, объявленного с модификатором readonly. Основное отличие в том, что поле хранит некоторое значение, которое не может изменить процесс чтения из этого поля. При чтении значения свойства есть возможность

выполнить заранее запланированные действия (вычисления), причём никаких

ограничений на характер этих действий (вычислений) не накладывается. Результат вычислений свойства может зависеть, например, от состояния среды, в которой выполняется программа, или от влияния процессов, выполняемых параллельно.

Пример поля с модификатором readonly: "дата рождения". Свойство "точный возраст" должно вычисляться с учётом конкретного момента обращения к этому свойству.

12.3. Автореализуемые свойства

Соседние файлы в папке CSBasicCourse2ndedPodbelsky