Терминология ООП

Отправной точкой во всей терминологии ООП является понятие класса. Классом называется шаблон, по которому создаются объекты.

Каждый объект, созданный на основе класса, называется экземпляром этого класса. Методы, свойства и процедуры событий, определенные внутри класса, называются членами. Предположим, вы пишете программу для работы с информацией о сотрудниках компании. Несомненно, в такой программе будет определен класс Employee; каждый экземпляр класса Employee будет соответствовать конкретному человеку. Члены класса Employee должны соответствовать специфике решаемых задач (например, в свойстве Name будет храниться имя работника, а метод Raise-Salary будет использоваться для повышения зарплаты).

 

Отношения между классами в программах

В традиционном ООП предусмотрены три типа отношений между классами:

В таких языках, как VB .NET, C# и Java, кроме классических типов существует четвертый тип отношений между классами — реализация интерфейса (отношение типа «поддерживает»). Суть реализации интерфейса заключается в том, что для поддержки некоторых функциональных возможностей ваш класс принимает на себя обязательства, по которым он должен содержать определенные члены. Интерфейсы существуют в VB начиная с версии 5 и часто используются в VB .NET. В главе 5 эта тема рассматривается гораздо подробнее.

Вернемся к классической тройке. Отношение использования, самое очевидное и распространенное, всего лишь означает, что один класс зависит от другого. Во всех ситуациях, когда один объект посылает сообщение другому объекту, можно говорить о зависимости между этими объектами. В обобщенном случае класс А использует класс Б, если:

или

Постарайтесь свести к минимуму количество взаимодействующих классов. Иначе говоря, избегайте лишних связей между классами, без которых можно обойтись. Если класс А не использует класс Б, то изменения в классе Б никак не отразятся на работе класса А (а следовательно, модификация класса Б не станет причиной ошибок в классе А!).

Термин «включение» (агрегирование) означает, что объект класса А содержит внутренние объекты класса Б.

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

Агрегирование по-прежнему широко используется в VB .NET, но во многих ситуациях ему на смену приходит наследование — третий тип отношений между классами. Наследование считается одним из четырех «краеугольных камней» ООП наряду с абстракцией, инкапсуляцией и полиморфизмом. Все четыре концепции будут рассмотрены в ближайших четырех разделах.

 

Абстракция

Абстракцией называется моделирование объектов в программе. Другими словами, речь идет об имитации реально существующих объектов, отражающей особенности их взаимодействия в окружающем мире. Так, первый объектно-ориентированный язык Simula (http://java.sun.com/people/jag/SimulaHistory.html) разрабатывался специально для задач имитации и моделирования. Впрочем, модные концепции виртуальной реальности выводят принцип абстракции на совершенно новый уровень, не связанный с физическими объектами. Абстракция необходима, потому что успешное использование ООП возможно лишь в том случае, если вы сможете выделить содержательные аспекты своей проблемы.

Приступая к построению объектной модели, всегда задавайте себе вопрос: какие свойства и методы должны входить в объект, чтобы он адекватно моделировал ситуацию для решения поставленной задачи?

 

Инкапсуляция

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

Вернемся к примеру с объектно-ориентированной программой для отдела кадров, в которой мы определили класс Employee. В переменных класса Еmplоуее могут храниться следующие сведения:

Чтобы изменить значения полей экземпляра, пользователи не обращаются к ним напрямую, а изменяют свойства и вызывают методы типа Rai seSalаrу. Разумеется, метод RaiseSalary будет изменять поле с текущей зарплатой, но в нетривиальном классе Employee он может работать с несколькими полями. Например, легко представить себе метод Rai seSalагу, который принимает решение о повышении зарплаты с учетом ее текущего уровня, рабочего стажа и личных достижений работника. Подведем итог. Инкапсуляция определяет функциональность объекта с точки зрения пользователя. Ее непосредственными проявлениями в VB .NETвыступают члены класса (методы, события и свойства).

Рискуя надоесть частыми повторениями, мы все же еще раз подчеркнем: успешное применение инкапсуляции возможно, если другие части вашей программы никогда не получают прямого доступа к полям экземпляра (переменным) ваших классов. Программа должна взаимодействовать с ними только через вспомогательные члены класса. Только при наличии закрытых данных, недоступных извне, объект превращается в «черный ящик» с четкими правилами поведения и неизвестным внутренним устройством. Ограничение доступа к данным имеет определяющее значение как для повторного использования, так и для надежности объекта при долгосрочном использовании.

 

Наследование

В качестве примера наследования представьте себе классы для отдельных категорий работников (класс Programmer, класс Manager и т. д.). Механизм, используемый для создания таких классов на базе класса Empl oyee, называется наследованием. В иерархии наследования класс Employee называется базовым, а класс Programmer — производным классом. Производные классы:

Например, метод RaiseSalary класса Manager может давать менеджеру большую прибавку к жалованию, чем метод RaiseSalary класса Programmer, при одинаковом стаже и личных показателях.

Производный класс может содержать новые методы, не имеющие аналогов в базовом классе.

Например, в класс Manager может быть включено новое свойство Secretary.

Разработчики давно хотели видеть наследование в VB и громко жаловались на его отсутствие. Нельзя сказать, что шум был поднят на пустом месте, однако многие склонны переоценивать важность наследования. Дело в том, что наследование, если хорошенько разобраться, всего лишь избавляет программиста от необходимости заново писать готовый код. В наследовании нет никакой мистики — это лишь способ упростить повторное использование программного кода. В нашем примере классы Employee и Manager обладали рядом сходных черт (наличие даты найма, зарплаты и т. д.). Зачем программировать свойство Salary в двух местах, если код будет абсолютно одинаковым? При полноценной реализации наследования использование функциональности базового класса в производном классе практически не требует дополнительных усилий — производный класс изначально наследует все члены своего предка. Программист может переопределить некоторые члены базового класса в соответствии со спецификой производного класса. Например, если менеджер автоматически получает 8-процентную прибавку к зарплате, тогда как для большинства работников прибавка составляет всего 4%, метод RaiseSalагу класса Manager должен заменить метод RaiseSalary базового класса Employee. С другой стороны, методы вроде GetName в изменении не нуждаются и остаются в прежнем виде.

Многие влиятельные теоретики ООП полагают, что от наследования вообще стоит дер-жаться подальше, и рекомендуют заменить его использованием интерфейсов (конечно, в VB .NET поддерживаются обе возможности). Такое отношение связано с проблемой неустойчивости базовых классов, которая в VB .NET отошла на второй план (за подробностями обращайтесь к главе 5). Использование интерфейсов вместо классического наследования иногда называется наследованием интерфейсов, тогда как за классическим наследованием закреплен термин «наследование реализации».

Напоследок мы хотим предупредить: не используйте наследование, если вы твердо не уверены в существовании логической связи «является частным случаем». Например, не создавайте класс Contractor (внештатный работник), производный от Employee, только для того, чтобы избавиться от хлопот по дублированию кода свойств имени или номера социального страхования. Внештатный работник не является служащим компании, и бухгалтер, простоты ради оформивший его по общим правилам, только наживет себе неприятности с налоговой инспекцией. То же относится и к вам: применение наследования при отсутствии логической связи «является частным случаем» приведет к печальным последствиям (см. главу 5).

 

Полиморфизм

В традиционной трактовке термин «полиморфизм» (от греческого «много форм») означает, что объекты производных классов выбирают используемую версию метода в зависимости от своего положения в иерархии наследования. Например, и в базовом классе Employee, и в производном классе Manager присутствует метод для повышения зарплаты работника. Тем не менее метод RaiseSalаrу для объектов класса Manager работает не так, как одноименный метод базового объекта Employee.

Классическое проявление полиморфизма при работе с классом Manager, производным от Empl oyee, заключается в том, что при вызове метода по ссылке на Empl oyee будет автоматически выбрана нужная версия метода (базового или производного класса). Допустим, в программе метод RaiseSalary вызывается по ссылке на Employee.

В VB5 и VB6 смысл термина «полиморфизм» был расширен, и к традиционному полимор-физму на базе наследования добавился полиморфизм на базе интерфейсов (объект, реализующий интерфейс, вызывал метод интерфейса вместо другого метода с тем же именем). Объект, реализующий интерфейс Manager, правильно выберет метод RaiseSalary в зависимости от контекста использования.

В обоих случаях объект выбирает метод в зависимости от полученного сообщения. При отправке сообщения не нужно знать, к какому классу фактически принадлежит объект; достаточно разослать сообщение всем объектам Employee и поручить выбор полиморфного метода компилятору.

Следующий пример показывает, почему полиморфизму придается такое большое значение. Одному из авторов доводилось консультировать компанию, занимавшуюся компьютерной обработкой медицинских анализов. Каждый раз, когда в процесс тестирования включался новый реактив, программистам приходилось просматривать многие тысячи строк кода, искать команды Select Case и добавлять в них секции Case для нового реактива. Стоило пропустить хотя бы одну... и нам бы не хотелось, чтобы этот реактив испытывался на наших анализах крови. Конечно, исправление многих команд Select Case превращало сопровождение в настоящий кошмар, требующий долгих часов тестирования.

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

Почему? Потому что в главной программе можно будет использовать конструкции следующего вида:

For Each reagent in Reagents

reagent.Method

Next

Показанный цикл будет автоматически работать с новым реактивом, а необходимость в долгих поисках Sel ect Case отпадет.

Select Case reagent Case iodine

' Действия с йодом Case benzene

' Действия с бензолом

' И т. д. для 100 разных случаев в 100 местах

В приведенном выше фрагменте цикл For Each перебирает все возможные реактивы, и благодаря волшебному свойству полиморфизма компилятор найдет метод, который должен вызываться для каждого конкретного реактива. Правильное использование полиморфизма избавит вас от громоздких команд Select Case, выбирающих нужное действие в зависимости от типа объекта.