Опубликован: 15.09.2010 | Доступ: свободный | Студентов: 4806 / 630 | Оценка: 3.97 / 3.80 | Длительность: 14:45:00
Лекция 8:

Наследование классов

< Лекция 7 || Лекция 8: 123 || Лекция 9 >

Абстрактные классы

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

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

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

Если в классе есть хотя бы один абстрактный метод, весь класс также должен быть описан как абстрактный, например:

abstract class Spirit
{
    public abstract void Passport();
}
class Monster : Spirit
{
    ...
    override public void Passport()
    {
        Console.WriteLine( "Monster {0} \t health = {1} ammo = {2}", 
                          name, health, ammo );
    }
    ...
}

class Daemon : Monster
{
    ...
    override public void Passport()
    {
        Console.WriteLine(
            "Daemon {0} \t health = {1} ammo = {2} brain = {3}", 
            Name, Health, Ammo, brain );
    }
    ... // полный текст этих классов приведен в главе 12
}

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

Бесплодные классы

В C# есть ключевое слово sealed, позволяющее описать класс, от которого, в противоположность абстрактному, наследовать запрещается:

sealed class Spirit
    {
        ...
    }
//  class Monster : Spirit { ... }           ошибка!

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

Вложение классов, когда один класс включает в себя поля, являющиеся классами, является альтернативой наследованию при проектировании. Например, если есть объект "двигатель", а требуется описать объект "самолет", логично сделать двигатель полем этого объекта, а не его предком.

Виды взаимоотношений между классами

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

Тимоти Бадд [1] приводит интересную классификацию форм наследования. Форма наследования определяет, с какой целью оно используется. Бадд считает, что порождение дочернего класса может быть выполнено по следующим причинам.

  • Специализация. Класс-наследник является специализированной формой родительского класса — в наследнике просто переопределяются методы.
  • Спецификация. Дочерний класс реализует поведение, описанное в родительском классе. В С# эта форма реализуется наследованием от абстрактного класса.
  • Конструирование. Класс-наследник использует методы базового класса, но не является его подтипом.
  • Расширение. В класс-потомок добавляют новые методы, расширяя поведение родительского класса.
  • Обобщение. Дочерний класс обобщает поведение базового класса. Обычно такое наследование используется в тех случаях, когда изменить поведение базового класса невозможно (например, базовый класс является библиотечным классом).
  • Ограничение. Класс-наследник ограничивает поведение родительского класса.
  • Варьирование. Базовый класс и класс-потомок являются вариациями на одну тему, однако связь "класс-подкласс" произвольна, например, "квадрат-прямоугольник" или "прямоугольник-квадрат". Эта форма фактически не отличается от "конструирования", так как класс-наследник, очевидно, "использует методы базового класса, но не является его подтипом".
  • Комбинирование. Дочерний класс наследует черты нескольких классов — это множественное наследование (в C# не используется, поскольку множественное наследование запрещено, а наследование от нескольких интерфейсов имеет иной смысл).

Альтернативой наследованию при проектировании классов является вложение, когда один класс включает в себя поля, являющиеся классами. Например, если есть класс "двигатель", а требуется описать класс "самолет", логично сделать двигатель полем этого класса, а не его предком. Вложение представляет отношения классов "Y содержит X" или "Y реализуется посредством Х" и обычно реализуется с помощью модели "включение-делегирование", которая иллюстрируется в листинге 8.4.

using System;
namespace ConsoleApplication1
{
    class Двигатель
    {   public void Запуск() 
        {
            Console.WriteLine( "вжжжж!!" );
        }
    }
    class Самолет
    {  public Самолет()
       {
            левый  = new Двигатель();
            правый = new Двигатель();
        }
        public void Запустить_двигатели()
        {
            левый.Запуск();
            правый.Запуск();
        }
        Двигатель левый, правый;
    }

    class Class1
    {   static void Main()
        {
        Самолет АН24_1 = new Самолет(); 
        АН24_1.Запустить_двигатели();
        }
    }
}
Листинг 8.4. Модель включения-делегирования

Результат работы программы:

вжжжж!!
вжжжж!!

В методе Запустить_двигатели запрос на запуск двигателей передается, или, как принято говорить, делегируется вложенному классу.

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

UML — Unified Modeling Language — является языком для специфицирования, визуализации, конструирования и документирования программных продуктов, а также используется в бизнес-моделировании и моделировании любых иных (не программных) систем.

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

Отношение наследования изображается на диаграмме классов в виде незакрашенного треугольника, направленного к базовому классу (предку). Пример изображения наследования с помощью диаграммы классов приведен на рис. 8.1.

Пример диаграммы UML для наследования

Рис. 8.1. Пример диаграммы UML для наследования

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

Примеры диаграмм UML для вложения классов

Рис. 8.2. Примеры диаграмм UML для вложения классов

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

< Лекция 7 || Лекция 8: 123 || Лекция 9 >
Stefan Berzan
Stefan Berzan
Молдова, Кишинев
Дмитрий Казимиров
Дмитрий Казимиров
Россия, Омск