Шаг 139.
Язык программирования C#. Начала
Наследование. Знакомство с наследованием

    На этом шаге мы рассмотрим простейший пример, иллюстрирующий основные идеи наследования.

    Идея наследования достаточно проста: вместо того чтобы создавать новый класс, так сказать, "на пустом месте", мы можем создать его на основе уже существующего класса. Класс, который используется как основа для создания нового класса, называется базовым. Класс, который создается на основе базового класса, называется производным. Главный (но далеко не единственный) эффект от создания производного класса на основе базового состоит в том, что открытые (и защищенные) члены из базового класса автоматически добавляются (то есть наследуются) в производный класс.


Здесь есть как минимум "экономия" на наборе программного кода: нет необходимости в теле производного класса заново вводить тот код, который наследуется из базового класса. Но имеются и другие положительные эффекты. Допустим, через какое-то время понадобится внести изменения в исходный код, реализованный в базовом классе. Если бы использовалась технология "копирования и вставки" кода, то правки пришлось бы вносить во всех местах, содержащих соответствующий код. Если же используется механизм наследования, то правки достаточно внести лишь в программный код базового класса. Есть и другие преимущества использования механизма наследования. Их мы также будем обсуждать.


Ранее упоминались защищенные члены класса. Защищенные члены класса описываются с ключевым словом protected. Защищенные члены класса, так же как и закрытые (описываются с ключевым словом private или без спецификатора доступа), доступны только в пределах программного кода класса. Защищенные члены класса от закрытых отличаются тем, что наследуются в производном классе. Особенности наследования членов базового класса с разным уровнем доступа описываются далее в шагах.

    Технически наследование реализуется достаточно просто. Базовый класс каким-либо особым образом описывать не нужно. Это самый обычный класс. В описании производного класса после названия класса через двоеточие указывается название базового класса. Так, если мы описываем класс MyClass и хотим, чтобы он был производным от уже существующего класса Base, то шаблон для описания класса MyClass будет выглядеть следующим образом:

class MyClass: Base {
  // Код производного класса
}

    В теле класса MyClass описываются только те члены, которые "добавляются" в дополнение к членам, наследуемым из класса Base. Повторно описывать члены класса Base в классе MyClass не нужно. При этом мы можем обращаться к унаследованным членам, хотя формально они в классе MyClass не описаны.


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

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


    Небольшой пример, в котором используется наследование классов, представлен в примере ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr139_1
{
    // Базовый класс: 
    class Base {
        // Открытое целочисленное поле: 
        public int code;
        // Открытый метод: 
        public void show() {
            // Отображение значения поля:
            Console.WriteLine("Поле code: " + code);
        }
    }

    // Производный класс: 
    class MyClass: Base {
        // Открытое символьное поле: 
        public char symb;
        // Открытый метод: 
        public void display() {
            // Отображение значения символьного поля: 
            Console.WriteLine("Поле symb: " + symb);
            // Вызов унаследованного метода: 
            show();
        }
        // Открытое свойство: 
        public int number {
            // Аксессор для считывания значения: 
            get {
                // Обращение к унаследованному полю: 
                return code;
            }
            // Аксессор для присваивания значения: 
            set {
                // Обращение к унаследованному полю: 
                code = value;
            }
        }
    }

    // Класс с главным методом: 
    class Program
    {
        static void Main()
        {
            // Создание объекта производного класса:
            MyClass obj = new MyClass();
            // Присваивание значений полям:
            obj.code = 100;
            obj.symb = 'A';
            // Вызов метода: 
            obj.display();
            // Использование свойства: 
            obj.number = 200;
            Console.WriteLine("Свойство number: " + obj.number);
            // Вызов метода: 
            obj.show();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы такой.


Рис.1. Результат выполнения программы

    Пример очень простой. Мы описали базовый класс Base, в котором есть целочисленное поле code и метод show() без аргументов, не возвращающий результат. При вызове метода в консольном окне отображается значение числового поля объекта, из которого вызывается метод.

    На основе класса Base путем наследования создается класс MyClass. Непосредственно в этом классе описано символьное поле symb, метод display() и целочисленное свойство number. При этом в программном коде класса MyClass мы использовали обращение к полю code и методу show(), хотя непосредственно в теле класса они не описывались. Эго стало возможным благодаря тому, что поле code и метод show() наследуются из класса Base. Поэтому в классе MyClass они присутствуют, и мы их можем использовать так, как если бы они были описаны в классе MyClass.

    Если более детально, то при вызове метода display() сначала отображается значение символьного поля объекта, после чего вызывается метод show(). Метод show(), в соответствии с тем, как он описан в классе Base, отображает в консольном окне значение целочисленного поля объекта, из которого метод вызывается (а вызывается он в данном случае из объекта производного класса).

    Свойство number в производном классе непосредственно связано с полем code, которое наследуется из базового класса. При считывании значения свойства number возвращается значение поля code, а при присваивании значения свойству number значение фактически присваивается полю code.

    В главном методе программы командой

            MyClass obj = new MyClass();
создается объект obj производного класса MyClass. Как видим, процедура создания объекта осталась такой же. Созданный объект obj имеет все поля, методы и прочие члены, которые описаны в классе MyClass или унаследованы в этом классе из базового класса Base. В частности, у объекта obj есть поля code и symb, методы display() и show(), а также свойство number. Командами
            obj.code = 100;
            obj.symb = 'A';
полям объекта присваиваются значения. При вызове метода display() (команда
            obj.display();
) в консольном окне отображаются значения полей объекта obj.

    Командой

            obj.number = 200;
значение присваивается свойству number, а в реальности новое значение получает поле code. При проверке значения свойства number с помощью команды
            Console.WriteLine("Свойство number: " + obj.number);
получаем такое же значение, как и при выполнении команды
            obj.show();    ,
которой отображается значение поля code.


В рассмотренном примере мы описали класс Base, но объекты на основе этого класса не создавались. Причина в том, что в объектах класса Base нет ничего особенного - обычные объекты обычного класса. Гипотетически, если бы на основе класса Base был создан объект, то у такого объекта было бы поле code и метод show(). И этот объект никак бы не был связан с объектом, созданным на основе класса MyClass. Наследование устанавливает связь между классами, а не между объектами.

    На следующем шаге мы рассмотрим наследование и уровни доступа.




Предыдущий шаг Содержание Следующий шаг