Урок № 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»), но в следующем уроке (который будет чисто практическим), мы немного разовьем данный пример, который покажет использование данной функции наследования в процессе выполнения программы.