Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Podbelsky_V_V_C_Bazovy_kurs.pdf
Скачиваний:
69
Добавлен:
02.06.2015
Размер:
1.73 Mб
Скачать

Средства взаимодействия

собъектами

12.1.Принцип инкапсуляции и методы объектов

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

Инкапсуляцию можно рассматривать как сокрытие особенностей реализации того или иного фрагмента программы от внешнего пользователя. Фрагмент должен быть доступен для обращений к нему только через внешний интерфейс фрагмента. Описания внешнего интерфейса должно быть достаточно для использования фрагмента. Если таким фрагментом является процедура (или функция), то нужно знать, как следует обратиться к процедуре, как передать ей необходимые входные данные и как получить результаты выполнения процедуры. Подробности внутренней реализации процедуры не должны интересовать того программиста, который использует процедуру. Именно принцип инкапсуляции лежит в основе запрета на использование в процедурах и функциях глобальных переменных. Ведь глобальная переменная определена вне процедуры и доступна внешним изменениям, не зависящим от обработки данных в ее теле.

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

1)возможность повторного использования объектов класса, например, в других программах (в этих других программах понадобится только знание внешнего интерфейса объектов класса);

2)возможность модифицировать внутреннюю реализацию класса без изменения тех программ, где применяются объекты этого класса;

Средства взаимодействия с объектами

217

 

 

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

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

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

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

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

218

Г л а в а 1 2

 

 

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

Средства взаимодействия с объектами

219

 

 

Всвязи с рассмотрением доступности членов класса полезно привести относящиеся к классам сведения по системе обозначений языка 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).

220

Г л а в а 1 2

 

 

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

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

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

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

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

Свойство – это член класса, который обеспечивает доступ

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

Средства взаимодействия с объектами

221

 

 

ванный со свойством участок памяти. Такой участок памяти выделяется для каждого поля класса, а свойство, связанное с этим полем, представляет собой упрощенное по сравнению с методами средство для получения либо задания, либо значения поля.

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

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

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

{

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

}

Вкачестве модификаторов свойства используются: new, public, protected, internal, private, static, virtual, sealed, override, abstract, extern

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

Тип_свойства – это тип той характеристики, которую представляет свойство.

Имя_свойства – идентификатор, выбираемый программистом для именования свойства.

Вторая часть объявления свойства (можно назвать ее телом объявления свойства) – это заключенная в фигурные скобки

222

Г л а в а 1 2

 

 

пара объявлений “методов” – аксессоров со специальными именами get и set.

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

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

Формат декларации аксессора изменения (set -аксессора):

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

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

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

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

return выражение;

Аксессор изменения (set-аксессор) подобен методу с возвращаемым значением типа void и единственным неявно заданным параметром, значение которого определяет новое значение свойства. Тип параметра определяется типом свойства. Имя параметра, которое используется в теле аксессора изменений, всегда value.

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

Средства взаимодействия с объектами

223

 

 

ствует соглашение (не обязательное) начинать имена свойств с заглавных букв. Если свойство представляет “во внешнем мире” конкретное поле класса, то имя свойства повторяет имя поле, но отличается от него первой заглавной буквой. Например, если в классе объявлено поле 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;

}

}

224

Г л а в а 1 2

 

 

Вклассе 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);

Средства взаимодействия с объектами

225

 

 

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. Результат изменения полей иллюстрирует предпоследняя

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]