- •Предисловие
- •Объектная ориентация программ на C#
- •1.1. Типы, классы, объекты
- •1.2. Программа на C#
- •1.3. Пространство имен
- •1.4. Создание консольного приложения
- •Типы в языке C#
- •2.1. Типы ссылок и типы значений
- •2.2. Классификация типов C#
- •2.3. Простые типы. Константы-литералы
- •2.4. Объявления переменных и констант базовых типов
- •Операции и целочисленные выражения
- •3.1. Операции языка C#
- •3.2. Операции присваивания и оператор присваивания
- •3.3. Операции инкремента (++) и декремента (--)
- •3.4. Выражения с арифметическими операциями
- •3.5. Поразрядные операции
- •Выражения с операндами базовых типов
- •4.1. Автоматическое и явное приведение арифметических типов
- •4.2. Особые ситуации в арифметических выражениях
- •4.3. Логический тип и логические выражения
- •4.4. Выражения с символьными операндами
- •5.2. Простые (базовые) типы C# как классы
- •Операторы
- •6.1. Общие сведения об операторах
- •6.2. Метки и оператор безусловного перехода
- •6.4. Операторы цикла
- •6.5. Операторы передачи управления
- •6.6. Переключатель
- •Массивы
- •7.1. Одномерные массивы
- •7.2. Массивы как наследники класса Array
- •7.3. Виды массивов и массивы многомерные
- •7.5. Массивы массивов и поверхностное копирование
- •Строки – объекты класса string
- •8.1. Строковые литералы
- •8.2. Строковые объекты и ссылки типа string
- •8.3. Операции над строками
- •8.5. Форматирование строк
- •8.6. Строка как контейнер
- •8.7. Применение строк в переключателях
- •8.8. Массивы строк
- •8.8. Сравнение строк
- •8.10. Аргументы метода Main()
- •Методы C#
- •9.2. Соотношение фиксированных параметров и аргументов
- •9.3. Параметры с типами ссылок
- •9.4. Методы с переменным числом аргументов
- •9.5. Перегрузка методов
- •9.6. Рекурсивные методы
- •9.7. Применение метода Array.Sort()
- •Класс как совокупность статических членов
- •10.1. Статические члены класса
- •10.2. Поля классов (статические поля)
- •10.3. Статические константы
- •10.4. Статические методы
- •10.5. Статический конструктор
- •10.6. Статические классы
- •Классы как типы
- •11.1. Объявление класса
- •11.2. Поля объектов
- •11.3. Объявления методов объектов
- •11.4. Пример класса и его объектов
- •11.5. Ссылка this
- •11.6. Конструкторы объектов класса
- •11.7. Деструкторы и финализаторы
- •Средства взаимодействия с объектами
- •12.1. Принцип инкапсуляции и методы объектов
- •12.2. Свойства классов
- •12.3. Автореализуемые свойства
- •12.4. Индексаторы
- •12.5. Индексаторы, имитирующие наличие контейнера
- •Включение, вложение и наследование классов
- •13.1. Включение объектов классов
- •13.2. Вложение классов
- •13.3. Наследование классов
- •13.4. Доступность членов класса при наследовании
- •13.5. Методы при наследовании
- •13.6. Абстрактные методы и абстрактные классы
- •13.7. Опечатанные классы и методы
- •13.8. Применение абстрактых классов
- •Интерфейсы
- •14.1. Два вида наследования в ООП
- •14.2. Объявления интерфейсов
- •14.3. Реализация интерфейсов
- •14.4. Интерфейс как тип
- •14.5. Интерфейсы и наследование
- •Перечисления и структуры
- •15.1. Перечисления
- •15.2. Базовый класс перечислений
- •15.3. Структуры
- •15.4. Упаковка и распаковка
- •15.5. Реализация структурами интерфейсов
- •Исключения
- •16.1. О механизме исключений
- •16.3. Свойства исключений
- •16.5. Исключения в арифметических выражениях
- •16.6. Генерация исключений
- •16.7. Пользовательские классы исключений
- •Делегаты и события
- •17.1. Синтаксис делегатов
- •17.2. Массивы делегатов
- •17.3. Многоадресные групповые экземпляры делегатов
- •17.4. Делегаты и обратные вызовы
- •17.5. Анонимные методы
- •17.6. События
- •Литература
- •Предметный указатель
Средства взаимодействия
собъектами
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. Результат изменения полей иллюстрирует предпоследняя