Урок № 22. Наследование и виртуальные методы


Виртуальные методы

Доброго времени суток! В этом уроке я продолжу рассказывать о таком важном механизме ООП (объектно-ориентированного программирования, если кто забыл) как наследование. В прошлом уроке, мы убедились, что наследование может значительно упростить жизнь программисту, дав возможность расширять функциональность уже существующих классов, не прикладывая лишних усилий (заново не изобретая велосипед). Но как я уже говорил, это не самая важная функция наследования (опять же, на мой взгляд). Вот сегодня я расскажу про вторую функцию наследования.

Если мы вспомним, то при наследовании классов есть базовый (родительский) класс, и класс наследник (причем, у класса наследника может быть только один базовый класс, но один класс может выступать в роли базового для нескольких классов наследников), так вот наследование, автоматически, устанавливает определенные отношения между классами. Так объекты класса наследника, могут восприниматься и как объекты базового класса. Иногда, это очень удобно!

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

class Лев : Животное
{
    //Тело класса
}

Как мы можем пользоваться этой возможностью? Да, например, таким образом:

//Базовый класс, описывающий оружие
class Weapon
{
    //Атака оружием
    public void Attack()
    {
        Console.WriteLine("Weapon Attack!");
    }
}

//Класс-наследник оружия, описывает нож
class Knife : Weapon
{
    //Атака ножом
    public void Attack()
    {
        Console.WriteLine("Knife Attack!");
    }
}

//Класс-наследник оружия, описывает ружьё
class Gun : Weapon
{
    //Атака ружьем
    public void Attack()
    {
        Console.WriteLine("Gun Attack!");
    }
}

class Program
{
    static void Main(string[] args)
    {
        //Создаем три объекта, по одному для каждого класса
        Weapon weapon = new Weapon();
        Knife knife = new Knife();
        Gun gun = new Gun();

        //Временная ссылка базового типа
        Weapon tmpWeapon = null;

        //Теперь переменная ссылается на объект базового класса "weapon"
        tmpWeapon = weapon;
        tmpWeapon.Attack();

        //Теперь переменная ссылается на объект класса наследника "knife"
        tmpWeapon = knife;
        tmpWeapon.Attack();

        //Теперь переменная ссылается на объект класса наследника "gun"
        tmpWeapon = gun;
        tmpWeapon.Attack();
    }
}

В приведенном примере, у нас есть три класса (один базовый и два его наследника), мы создаем по одному объекту каждого из них, а также создаем временную ссылку базового типа.

Обратите внимание, на то, как мы создаем и инициализируем переменную «tmpWeapon», после знака «=», мы написали ключевое слово null, это означает, что переменная не указывает, т.е. не ссылается ни на какой объект). В данном случае – это что-то вроде пустой ссылки.

После чего, мы «заставляем ссылаться» временную переменную «tmpWeapon» по очереди на все три объекта и в каждом случае, вызываем метод  «Attack». И по задумке, в тот момент, когда переменная ссылается, на объект класса «Knife», должен вызываться метод «Attack» класса «Knife», в тот момент, когда переменная ссылается, на объект класса «Gun», должен вызываться метод «Attack» класса «Gun». Но на самом деле, во всех трех случаях, вызывается метод базового класса, т.е. класса «Weapon».

Первый результат работы программы

Первый результат работы программы

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

Стоит нам слегка изменить код трех классов следующим образом, как все заработает так как мы предполагали сразу:

//Базовый класс, описывающий оружие
class Weapon
{
    //Атака оружием
    public virtual void Attack()
    {
        Console.WriteLine("Weapon Attack!");
    }
}

//Класс-наследник оружия, описывает нож
class Knife : Weapon
{
    //Атака ножом
    public override void Attack()
    {
         Console.WriteLine("Knife Attack!");
    }
}

//Класс-наследник оружия, описывает ружьё
class Gun : Weapon
{
    //Атака ружьем
    public override void Attack()
    {
        Console.WriteLine("Gun Attack!");
    }
}

Обратите внимание на выделенные строки кода. В них и заключаются все изменения. А точнее, изменения кроются в трех словах: при объявлении метода «Attack» в базовом классе мы добавили ключевое слово virtual (показали, что он виртуальный), а в классах наследниках, добавили слово override (показали, что мы переопределяем данный метод базового класса).

Теперь, если запустить приложение, то вы увидите примерно следующее:

Второй результат работы программы

Второй результат работы программы

Вот так вот можно использовать наследование. Естественно, данный пример слегка надуманный (и мы бы могли обойтись без временной переменной «tmpWeapon»), но в следующем уроке (который будет чисто практическим), мы немного разовьем данный пример, который покажет использование данной функции наследования в процессе выполнения программы.

Перейти к следующему уроку