Неформальное введение в объектно-ориентированное программирование на платформе .NET Framework 6

Неформальное введение в объектно-ориентированное программирование на платформе Net.Framework

Продолжение. Начало
в КГ №№ 13, 14, 16, 17, 20, 23

Полиморфизм
У нас на очереди третий столп объектно-ориентированного программирования, называющийся полиморфизм. Итак, если вы набирали код домашнего задания, то наверняка обратили внимание на странно выглядящее объявление типа объектов:

MVD_Worker omon1 =new Omonovez(“Федя”,true,true); MVD_Worker gai1 =new GIBDD(false);
Заметьте, мы объявляем переменную будущего объекта как имеющую тип MVD_Worker. Создаем же мы экземпляр класса с помощью конструкторов классов Omonovez и GIBDD! То есть создаем экземпляр совершенно другого класса. На первый взгляд это выглядит как ошибка. Тем не менее, Visual Studio ничего не имеет против такой записи и успешно компилирует наш код. Ошибка "Майкрософт"? — подумаете вы. Нет, — отвечу я, — просто неизученная еще нами возможность.
Полиморфизм и заключается в возможности присваивать переменной базового типа класса экземпляр потомка этого класса. На первый взгляд эта возможность выглядит скромно и не производит на начинающего программиста особого впечатления. Ну и что, спрашивает он, какие преимущества это мне дает? Давайте вместе поиграемся этими возможностями и посмотрим, насколько они хороши.
Для того чтобы обыграть сразу все нюансы на одном примере кода, не захламляя газету километровыми листингами, я приведу в этой статье единственный пример. В силу своей единственности он будет всеобъемлющим. Поэтому не пугайтесь, встретив в листинге непонятные вам пока нюансы. По ходу изложения материала я расскажу вам обо всем новом и непонятном.
Итак, приступим. Все классы, как обычно, созданы в отдельных файлах. Имя файла совпадает с именем класса и имеет расширение .cs. Как создавать классы и располагать их в отдельных файлах, я подробно рассказал в предыдущей статье и снова повторять не буду.
Основным базовым классом нашего проекта является класс MVD_Worker, описываемый таким кодом:

public class MVD_Worker { private bool internal HasDocument;
public virtual bool HasDocument { get{return internal HasDocument;} } protected virtual bool G
unIsReady { get{return false;} } 
protected virtual void MakeShoot() { 
}
public virtual void Shoot() { if(GunIsReady){ MakeShoot(); } }
public bool Check Document() { return HasDocument; } public MVD_Worker() { internalHasDocument
=false; } public MVD_Worker( bool WorkerHasDocument) { internalHasDocument=WorkerHasDocument; } }
От ранее использовавшегося нами прототипа он отличается наличием бестолкового, на первый взгляд, метода CheckDocument. Этот метод просто возвращает содержимое свойства HasDocument, фактически дублируя его работу. От самого свойства его отличает то, что он объявлен как обычный метод класса, а свойство у нас является виртуальным, то есть свойство HasDocument можно при желании перехватить в классе-потомке, а метод CheckDocument — нет. Помимо этого, в классе имеются два новых метода, MakeShoot и Shoot, а также новое свойство GunIsReady. Об их предназначении я вам расскажу ближе к концу статьи.
Также у нас есть класс — потомок MVD_Worker, называющийся Omonovez. Выглядит он следующим образом:

public class Omonovez:MVD_Worker { private string internalName = “Unnamed”;
public override bool HasDocument { get{return true;} } protected override bool GunIsReady { ge
t{return true;} } 
protected override void MakeShoot() { // êîä âûñò&e
th;åëà. }
public string Name { get{return internal Name;} } public Omonovez(): base(true) { 
} public Omonovez(string Name):this() { internalName=Name; } }
От ранее использовавшегося нами класса его отличает усеченный набор свойств, я оставил ему лишь имя. В качестве компенсации классу добавлено переопределение базового свойства HasDocument. Наш Omonovez обрабатывает его самостоятельно, сразу возвращая true. Так как свойства усечены, класс имеет всего два конструктора. Один базовый без параметров и один принимающий в качестве своего параметра строчку с именем бойца. Для сокращения кода я использовал присваивание имени бойца по умолчанию сразу в момент объявления переменной.
private string internalName = "Unnamed";
Подобная техника вполне допустима и не считается неправильной в C#. Она имеет свои плюсы и минусы. Основной плюс очевиден — краткость записи. Основной минус заключается в том, что указанные таким образом присваивания неочевидно производятся в конструкторе объекта. На мой взгляд, неочевидностей в коде следует, по возможности, избегать. Такое присваивание возможно и не во всех случаях — например, оно не сработает, если вы объявляете таким образом статическую (static) переменную. На мой взгляд, эти два способа инициализации переменных демонстрируют извечную борьбу между наглядностью кода и ленью программиста. Я сам также ленив и поэтому время от времени объявляю свойства и в такой, краткой форме.
Следующий класс, участвующий в нашем проекте, это GIBDD — прямой потомок MVD_Worker. Основное его отличие от базового класса заключается в том, что он проверяет у работника ГИБДД не только наличие удостоверения, но еще и наличие прав на вождение автомобиля. Для того чтобы добиться такой функциональности, мы переопределили свойство hasDocument, и все необходимые для проверки делаем внутри него. Класс имеет всего один конструктор с параметром, указывающим, есть ли права у данного гаишника. Базовый конструктор я не стал описывать намеренно. При его отсутствии нам не удастся создать экземпляр объекта, не присвоив ему явно наличие или отсутствие прав.
Код класса выглядит вот так:

public class GIBDD:MVD_Worker { private bool internalHasPrava=false; 
public bool HasPrava { get{return internalHasPrava;} }
public override bool HasDocument { get { if(base.HasDocument) { return internal HasPrava; } el
se{return false;} } }
public GIBDD(bool hasPrava):base(true) { internalHasPrava=hasPrava; } }
Помимо указанных классов, в нашем проекте имеется и запускающий основной файл приложения. Оформите вы его как консольное приложение или набросаете на форму компонентов и сделаете приложение WinForms, это ваше личное дело. Единственное, что от вас требуется, это описать в своем коде процедуру, в которую вы будете вставлять впоследствии код, который я вас попрошу. Если вы создаете WinForms-приложение, бросьте на форму кнопку и дважды по ней щелкните мышкой. Сформируется обработчик нажатия на эту кнопку. Он нам вполне подойдет. Если у вас консольное приложение, оформите удобным для вас образом процедуру и вызывайте ее из метода Main. Короче говоря, как вам удобно, так и сделайте... Готовы? ОК. Теперь давайте занесем в эту вашу процедуру код, работу которого мы с вами будем исследовать. Выглядит он так:

bool hasDocument; MVD_Worker work1=new MVD_Worker(); hasDocument = work1.CheckDocument(); MVD_
Worker omon1=new Omonovez(“Федя”); hasDocument = omon1. CheckDocument(); MVD_Worker gai1 = new GIBDD
(false); hasDocument = gai1. CheckDocument(); omon1.Shoot();
Обратите внимание: в коде отсутствуют какие-либо средства для вывода состояния объекта на экран. Мы с вами теперь не маленькие, уже изучили общие принципы отладки в Visual Studio и теперь можем без проблем посмотреть значение любых свойств с помощью ее встроенного отладчика. Захламлять же вывод нашей программы на экран, да еще специально писать для этого лишний код нам больше нет никакой необходимости.
Установите точку остановки на первую строчку нашего тестового кода (MVD_Worker work1=new MVD_Worker();) и запускайте приложение на исполнение с помощью команды Debug-> Start (F5). Приложение стартует, вы жмете на кнопку, обработчиком нажатия которой является наш код, выполнение программы приостанавливается, и перед вами снова появляется редактор с нашим тестовым кодом. Ваша точка остановки перекрасилась в желтый цвет. На ее красном кружочке слева появилась желтая стрелочка, индицирующая инструкцию в коде, которую отладчик собрался выполнить. На всякий случай уточню: не "выполнил", а "собрался выполнить". У вас все так? Если нет, вы что-то сделали неправильно. Перечитайте в этой статье главу, озаглавленную "Отладка приложений в Visual Studio", еще разок.
Давайте оглядимся вокруг. Помимо окна редактора, перед вами должно находиться еще несколько окон. Нас интересуют описанные мной ранее Autos, Local и Watch. Если они по какой-то причине не появились, достаньте их самостоятельно. Для этого идете в пункт меню Debug-> Window и щелкаете по соответствующим пунктам.

Я создавал приложение WinForms, и поэтому у меня в окне Autos имеется два пункта: this — ссылка на объект {WindowsApplication1.Form1} (это наша форма, в методе которой мы сейчас и находимся) и work1, помеченный как <undefined value> . Напротив this имеется "плюсик", попробуйте по нему щелкнуть. "Оба-на!" — воскликнут любители поэкспериментировать. Раскроется дерево, содержащее свойства всех вложенных в нашу форму объектов: там есть и кнопка, по которой вы щелкали, и список лога, если вы поленились его убрать с формы предыдущих примеров. Как я и рассказывал выше, вы можете посмотреть или изменить любое из этих свойств. К примеру, надпись на кнопке.

Второй объект (work1) — это наша переменная типа MVD_Worker, в которую мы собираемся создать новый объект. Помечена она как <undefined value> потому, что этого еще не произошло. Сейчас переменная еще не содержит никаких объектов, поэтому ее значение на данный момент неопределенно. Давайте-ка это исправим и приступим к пошаговому исполнению нашей программы. Нас интересует подробное изучение происходящего в нашем коде, поэтому мы воспользуемся командой Step Into (F11). Нажимаем F11 и оказываемся в теле конструктора объекта MVD_Worker без параметров. Нажимаем F11 повторно, и курсор перемещается на инструкцию, присваивающую переменной intHasDocument значение false. Обратите внимание на значение этой переменной. Нажимаем F11 еще раз, и курсор переместится на закрывающую скобку. В окне Autos мы можем проконтролировать значение переменной intHasDocument. Как и следовало ожидать, оно равняется false. Еще раз нажимаем F11, и нас возвращают обратно на строчку нашего тестового примера. Привычно нажимаем F11 еще раз, и желтый курсор переместится на следующую строчку кода, в которой мы пробуем присвоить значение переменной hasDocument с помощью метода work1.Check Document().

Остановимся на минутку и изучим содержимое окна Autos. Так как мы с вами перешли на новую строчку, в которой упоминается переменная hasDocument, она появилась в этом окне. Также объект work1 перестал быть <undefined value>, а стал {WindowsApplication1.MVD_Worker}. Напротив него появился "плюсик", раскрыв который мы можем посмотреть на то, чему сейчас равняются его свойства intHasDocument и hasDocument. Все указанные поля сейчас равны false. Ну что же, вполне ожидаемое поведение. Для тренировки попробуйте самостоятельно пройти с помощью F11 следующую строчку кода. Ту самую, с присваиванием переменной hasDocument значения, возвращаемого методом work1.CheckDocument(). Никаких неожиданностей вам встретиться не должно. Вы сначала зайдете в тело метода CheckDocument(), потом из него прыгнете в метод get{}, возвращающий значение свойства, а затем вернетесь обратно: сначала в метод Check Document(), а затем и в наш тестовый код на строчку создания экземпляра объекта omon1, принеся "в клюве" значение свойства intHas Document. Вот вы и получили наглядное подтверждение ваших знаний того, как работают свойства и методы классов.

Продолжение следует.
Герман Иванов



Компьютерная газета. Статья была опубликована в номере 24 за 2003 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета