Опубликован: 18.05.2011 | Доступ: свободный | Студентов: 965 / 104 | Оценка: 4.40 / 4.20 | Длительность: 12:30:00
Лекция 4:

Наследование и полиморфизм

< Лекция 3 || Лекция 4: 123 || Лекция 5 >
Аннотация: Рассматриваются такие технологии объектно-ориентированного подхода, как наследование и полиморфизм. Приведены примеры на языке C#, демонстрирующие эти технологии.

Цель лекции: Показать возможности технологий наследования и полиморфизма при проектировании классов. Научится применять эти технологии при программировании на языке C#.

Чтобы продемонстрировать механизм наследования классов рассмотрим несколько нарочито простых классов, которые будут описывать транспортные средства: автобус, такси, троллейбус и трамвай. Базовым классом у нас будет класс "траспортное средство", который мы определим следующим образом:

\begin{verbatim}
    class TTransport
    {
        protected int Speed; // скорость
        protected int Massa; // Масса
        protected int Payload; // грузоподъемность

        public TTransport(int Speed, int Massa, int Payload)
        {
            this.Speed = Speed;
            this.Massa = Massa;
            this.Payload = Payload;
        }

        public void Print() // распечатать информацию
        {
            Console.WriteLine("Speed = {0}", Speed);
            Console.WriteLine("Massa = {0}", Massa);
            Console.WriteLine("Payload = {0}", Payload);
        }
    }
\end{verbatim}

Смысл этого класса хранить и распечатывать информации о транспортном средстве. Любое транспортное средство имеет такие характеристики как скорость, масса и грузоподъемность. Однако для автотранспорта есть еще дополнительные характеристики такие, как расход топлива, а у электротранспорта есть такая дополнительная характеристика как напряжение. Поэтому мы создадим еще два класса. С использованием технологии наследования. Наследование - это технология проектирования классов, позволяющая создавать новые классы на базе предыдущих классов. При этом классы наследники будут иметь все поля и методы родительского класса. А к тем полям и методам, которые имеют спецификатор доступа public или protected, можно будет обращаться из наследного класса. Приведем определения этих классов:

\begin{verbatim}
    class TAuto : TTransport
    {
        protected int Petrol; // Расход бензина

        public TAuto(int Speed, int Massa, int Payload,
        int Petrol)
            : base(Speed, Massa, Payload)
        {
            this.Petrol = Petrol;
        }

        public void Print()
        {
            base.Print();
            Console.WriteLine("Petrol = {0}", Petrol);
        }
    }

    class TElectro : TTransport
    {
        protected int Voltage; // Напряжение

        public TElectro(int Speed, int Massa, int Payload,
        int Voltage)
            : base(Speed, Massa, Payload)
        {
            this.Voltage = Voltage;
        }

        public void Print()
        {
            base.Print();
            Console.WriteLine("Voltage = {0}", Voltage);
        }
    }
\end{verbatim}

Разберем, как работает наследование класса. Чтобы указать, что класс B является наследником класса A, следует написать:

\begin{verbatim}
    class B : A
    {
        ...
    }
\end{verbatim}

Теперь класс B имеет все поля и методы класса A. Однако поля и методы класса A, имеющие классификатор доступа private, будут недоступны для класса B. Если в классе A объявлены поля и методы, совпадающие с полями и методами класса B, то они будут заменены, или как говорят - сокрыты соответствующими полями и методами класса B. В нашем примере таким методом является метод Print. Если нам нужно из класса B обратиться к полям или методам родительского класса A, то для этого нужно использовать ключевое слово base, аналогичное ключевому слову this.

Если родительский класс имеет заданный конструктор, то он должен быть вызван из конструктора наследного класса. Обратите внимание, как можно вызывать конструктор родительского класса:

\begin{verbatim}
        public TAuto(int Speed, int Massa, int Payload,
        int Petrol) : base(Speed, Massa, Payload)
        { ... }
\end{verbatim}

Попрактикуемся в создании классов наследников. Создадим два класса (автомобиль и такси) наследников от класса TAuto и два класса (трамвай и троллейбус) наследников от класса TElectro. У каждого нового класса могут быть дополнительные поля и методы, но мы для простоты не будем добавлять новых полей и методов.

\begin{verbatim}
class TBus : TAuto
{
    public TBus(int Speed, int Massa, int Payload,
    int Petrol) : base(Speed, Massa, Payload, Petrol) { }
}

class TTaxi : TAuto
{
    public TTaxi(int Speed, int Massa, int Payload,
    int Petrol) : base(Speed, Massa, Payload, Petrol) { }
}

class TTram : TElectro
{
    public TTram(int Speed, int Massa, int Payload,
    int Voltage) : base(Speed, Massa, Payload, Voltage) { }
}

class TTroll : TElectro
{
    public TTroll(int Speed, int Massa, int Payload,
    int Voltage) : base(Speed, Massa, Payload, Voltage) { }
}
\end{verbatim}

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

\begin{verbatim}
            TTransport[] Trans;
            Trans = new TTransport[4];
            Trans[0] = new TAuto(120, 5, 2, 60);
            Trans[1] = new TTaxi(250, 1, 1, 40);
            Trans[2] = new TTram(100, 7, 5, 3000);
            Trans[3] = new TTroll(80, 4, 2, 2500);
\end{verbatim}

Заметим, что у нас в массиве Trans все элементы из разных классов, хотя и родственники класса TTransport. Конечно, мы могли бы, перебирая массив Trans, для каждого экземпляра класса подсчитать нужное соотношение. Но тут возникает проблема - ведь у половины классов нужно делить значение Payload на значение Petrol, а у половины нужно делить значение Payload на значение Voltage. Тем более, что идеология объектно-ориентированного программирования подразумевает, что классы сами должны обрабатывать собственные данные. Поэтому мы поступим следующим образом - мы добавим в наши классы метод Calc(). При этом мы воспользуемся технологией полиморфизма. Полиморфизм - это возможность объектов с одинаковой спецификацией иметь различную реализацию. Мы создадим у класса TTransport виртуальный метод:

\begin{verbatim}
    class TTransport
    {
        ...
        public virtual double Calc()
        {
            return 0;
        }
    }
\end{verbatim}

Ключевое слово virtual означает, что наследники данного класса смогут изменить код данного метода. Сейчас мы еще модифицируем у родительского класса TTransport метод Print следующим образом:

\begin{verbatim}
        public void Print() // распечатать информацию
        {
            Console.WriteLine("Speed = {0}", Speed);
            Console.WriteLine("Massa = {0}", Massa);
            Console.WriteLine("Payload = {0}", Payload);
            Console.WriteLine("Calc Result = {0}", Calc());
        }
\end{verbatim}
Теперь распечатаем наш массив следующим образом:
\begin{verbatim}
            foreach (TTransport T in Trans)
            {
                T.Print();
                Console.WriteLine();
            }
\end{verbatim}
Мы увидим следующее:
\begin{verbatim}
Speed = 120 Massa = 5 Payload = 2 Calc Result = 0

Speed = 250 Massa = 1 Payload = 1 Calc Result = 0

Speed = 100 Massa = 7 Payload = 5 Calc Result = 0

Speed = 80 Massa = 4 Payload = 2 Calc Result = 0
\end{verbatim}

Разумеется, везде мы видим "Calc Result = 0", потому что мы еще нигде не реализовали метод вычисления нужного нам коэффициента. Но вспомним, что мы пометили метод Calc как виртуальный, и теперь мы можем заменить этот метод у классов потомков. Сделаем следующие модификации наших классов:

\begin{verbatim}
    class TAuto : TTransport
    {
        ...

        public override double Calc()
        {
            return (double)Payload / (double)Petrol;
        }
    }

    class TElectro : TTransport
    {
        ...

        public override double Calc()
        {
            return (double)Payload / (double)Voltage;
        }
    }
\end{verbatim}

Теперь наша программа выдаст нужный результат:

\begin{verbatim}
Speed = 120 Massa = 5 Payload = 2 Calc Result = 0,0333333333333333

Speed = 250 Massa = 1 Payload = 1 Calc Result = 0,025

Speed = 100 Massa = 7 Payload = 5 Calc Result = 0,001666666666667

Speed = 80 Massa = 4 Payload = 2 Calc Result = 0,0008
\end{verbatim}

Чтобы оценить всю мощь полиморфизма, нужно обратить внимание, что метод Calc вызывается в методе Print класса TTransport! Ведь при наследовании мы можем и не иметь исходного текста класса TTransport, этот класс может быть доступен только в виде откомпилированной библиотекой. Тем не менее механизм полиморфизма позволяет нам модифицировать виртуальные методы. Мы будем использовать систематически полиморфизм при реализации вычислительных процедур.

Сделаем еще одно замечание. В родительском классе TTransport нам пришлось фиктивно реализовать метод Calc, хотя с точки зрения проектирования это не очень правильно. Во-первых, плохо писать код только для того, чтобы он был, а, во-вторых, плохо писать неверный код: возвращать 0. К счастью, развитое объектно-ориентированное программирование позволяет создавать так называемые абстрактные классы. Абстрактные классы могут содержать абстрактные виртуальные методы. Абстрактный метод содержит только объявление, но не имеет реализации. Разумеется, абстрактный метод является виртуальным, но ключевое слово virtual не пишется, чтобы в наследных классах можно было перекрыть этот метод. Более того, невозможно создать экземпляр абстрактного класса, а наследник абстрактного класса или сам должен быть абстрактным или обязан реализовать все абстрактные методы. Последний штрих к нашей программе:

\begin{verbatim}
    abstract class TTransport
    {
        ...

        public abstract double Calc();
    }

\end{verbatim}

Еще раз подчеркнем, что мы не привели в этом листинге реализацию метода Calc не потому, чтобы сократить письмо, а потом что для абстрактного метода не бывает реализации.

Ключевые термины

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

Наследование - технология проектирования классов, позволяющая создавать новые классы на базе предыдущих классов.

Полиморфизм - возможность объектов с одинаковой спецификацией иметь различную реализацию.

Краткие итоги: На примерах проектирования классов продемонстрированы технологии наследования и полиморфизма в языке C#. Показано, как работать с абстрактными классами.

< Лекция 3 || Лекция 4: 123 || Лекция 5 >