- •Лекция 2. С# и объектно-ориентированное программирование Формальное определение класса в с#
- •Ссылки на самого себя
- •Определение открытого интерфейса по умолчанию
- •Указание области видимости на уровне типа: открытые и внутренние типы
- •Столпы объектно-ориентированного программирования
- •Инкапсуляция
- •Наследование: отношения «быть» и «иметь»
- •Полиморфизм: классический и для конкретного случая
- •Средства инкапсуляции в с#
- •Реализация инкапсуляции при помощи традиционных методов доступа и изменения
- •Второй способ инкапсуляции: применение свойств класса
- •Поддержка наследования в с#
- •Применение модели включения-делегирования
- •Поддержка полиморфизма в с#
- •Абстрактные классы
- •Принудительный полиморфизм: абстрактные методы
- •Приведение типов в с#
- •Приведение числовых типов
- •Обработка исключений
- •Жизненный цикл объектов
- •Завершение ссылки на объект
- •Интерфейс iDisposable
- •Взаимодействие со сборщиком мусора
Применение модели включения-делегирования
Как уже говорилось, в объектно-ориентированных языках программирования исполъзуются две главные разновидности наследования. Первая из них называется классическим наследованием (моделью «быть» — is-a), и эта модель была рассмотрена в предыдущем разделе. Вторая разновидность — это модель включения-делегирования (модель «иметь» — has-a), и именно ей посвящен настоящий раздел.
Для начала нам потребуется простой класс, представляющий автомобильный радиоприемник:
// Этот класс будет внутренним, включенным в другой класс - Саг
pubic class Radio
{
public Radio( ){}
public void TurnOn(bool on)
{
if(on)
Console.WriteLine(“Jamming. ");
else
Console.WriteLine(“Quiet time . ");
}
}
Теперь предположим, что вам потребовалось разработать программную модель автомобиля. Класс Саг, который мы будет использовать для этой цели, должен содержать сведения об автомобиле (как мы его именуем, его скорость в настоящий момент и максимально допустимую скорость). Все эти данные можно задавать при помощи пользовательского конструктора. Определение класса Саг может выглядеть следующим образом:
//Этот класс будет выступать в роли внешнего класса, класса-контейнера для Radio
public class Car
{
private int currSpeed;
private int maxSpeed;
private string petName;
bool dead; // Жива ли машина или уже нет
public Саг()
{
maxSpeed = 100;
dead = false;
}
public Car(string name, int max, int curr)
{
currSpeed = curr;
maxSpeed = max;
petName = name;
dead = false;
}
public void SpeedUp (int delta)
{ …… }
Сейчас в нашем распоряжении есть два независимых класса — Radio для авто мобильного радиоприемника и Саг для самого автомобиля Понятно, что эти два класса должны взаимодействовать друг с другом и эти отношения желательно как то зафиксировать Однако вряд ли нам удаст ся применить в этом случае класси ческое наследование трудно производить машину от радиоприемника ичи радио приемник от машины В этой ситуации, конечно больше подойдет отношение включения-делегирования пусть машина включает в себя радио и передает этому классу необходимые команды В терминологии объектно ориентированного про граммирования контейнерный класс (в нашем случае Саг) называется родитель ским (parent), а внутренний класс, который помещен внутрь контейнерного (это конечно Radio), называется дочерним (child).
Помещение радиоприемника внутрь автомобиля влечет за собой внесение в опре деление класса Саг следующих изменений:
// Автомобиль «имеет» (has а) радио
public class Car
{
. . . . . .
// Внутреннее радио
private Radio theMusicBox;
}
Обратите внимание, что внутренний класс Radi о был объявлен как private С точки зрения инкапсуляции мы делаем все правильно Однако при этом неизбежно возникает вопрос: а как нам включить радио? Переводя на язык программировая — а как внешний мир будет взаимодействовать с внутренним классом? Понятнo, что ответственность за создание объекта внутреннего класса несет внешний контейнерный класс. В принципе код для создания объектов внутреннего класса можно помещать куда угодно, но обычно он помещается среди конструкторов контейнерного класса:
// За создание объектов внутренних классов ответственны контейнерные классы
public class Car
{
. . .
// Встроенное радио
private Radio theMusicBox;
public Car( )
{
maxSpeed = 100;
dead = false;
// Объект внешнего класса создаст необходимые объекты
//внутреннего класса при собственном создании
theMusicBox = new Radio( ); // Если мы этого не сделаем theMusicBox
// начнет свою жизнь с нулевой ссылки
}
public Car(string name, int max, int curr)
{
currSpeed = curr;
maxSpeed = max;
petName = name;
dead = fales;
theMusicBox = new RadioO;
}
. . . .
}
Произвести инициализацию средствами С# можно и так:
// Автомобиль «имеет» (has-a) радио
public class Car
{
. . . .
// Встроенное радио
private Radio theMusicBox = new Radio( );
. . . .
}
Таким образом, радиоприемник внутри автомобиля у нас теперь создается есте с автомобилем. Однако вопрос о том, как именно можно включить этот раприемник, остался нерешенным. Ответ на него выглядит так:- для того чтобы воспользоваться возможностями внутреннего класса, необходимо делегирование (delegation). Делегирование заключается в простом добавлении во внешний контейнерный класс методов для обращения ко внутреннему классу, шример:
// Во внешний класс добавляются дополнительные открытые методы и другие члены
// которые обеспечивают доступ к внутреннему классу
public class Car
{
. . . .
public void CrankTunes(bool state)
{
// Передаем (делегируем) запрос внутреннему объекту
theMusicBox.TurnOn(state);
}
}
В приведенном ниже коде обратите внимание на то, что пользователь косвенно обращается к скрытому внутреннему объекту, даже не подозревая о том, что в недрах объекта Саг существует закрытый (определенный как private) объект Radio:
// Выводим автомобиль на пробную поездку
public class CarApp
{
public static int Main(string[ ] args)
{
// Создаем автомобиль (который, в свою очередь, создаст радио)
Саг c1;
c1 = new Car( “SlugBug”, 100, 10);
// Включаем радио (запрос будет передан внутреннему объекту)
c1.CrankTunes(true);
// Ускоряемся
for(int i=0; i < 10; i++)
c1.SpeedUp(20);
// Выключаем радио (запрос будет вновь передан внутреннему объекту
c1.CrankTunes(false);
return 0;
}
}