yandex rtb 1
ГоловнаЗворотній зв'язок
yande share

Объектно-ориентированное программирование

Лекция 2.

Принципы объектно-ориентированного программирования.

 

Все языки объектно-ориентированного программирования обеспечивают механизмы, которые помогают реализовывать объектно-ориентированную модель. К ним относятся инкапсуляция, наследование и полиморфизм.

Буч определяет инкапсуляцию следующим образом:

Инкапсуляция, encapsulation - процесс разделения элементов абстракции, которые образуют ее структуру и поведение. Служит для отделения внешних обязательств объекта от его реализации.

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

Обычно при проектировании объектов стремятся инкапсулировать как можно большую часть класса. Это делается по следующим причинам:

·         Закрытые данные объекта не могут изменяться пользователем, что уменьшает вероятность вмешательства в работу объекта или зависимости пользовательского кода от внутренней структуры объекта. Если пользовательский код зависит от структуры объекта, изменение объекта может нарушить работу пользовательского кода.

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

·         Объемные интерфейсы усложняют всю систему. Доступ к закрытым данным возможен только внутри самого класса; к открытым данным можно обратиться извне через любой объект. Как правило, наличие большого числа открытых членов класса заметно усложняет отладку программы.

Использование инкапсуляции дает возможность использовать внутреннюю структуру объекта, не соответствующую представлению об этом объекте. Рассмотрим, например, автоматическую трансмиссию автомобиля. Она инкапсулирует информацию о машине, например, о величине ускорения, позицию рукоятки передач и т.п. Как пользователь, вы имеете только один метод воздействия – перемещение рукоятки передач. Невозможно, например, повлиять на трансмиссию, используя сигнал поворота. Таким образом, рукоятка – интерфейс к трансмиссии.

Определим интерфейс следующим образом:

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

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

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

Для того чтобы скрыть реализацию класса, каждое поле и каждый метод класса должен быть помечен как private (частный или локальный) или public (общий). Методы и поля с модификатором private могут быть доступны только в коде, являющемся членом данного класса. Модификатор public указывает на все, что нужно или можно знать пользователям класса. При этом настоятельно не рекомендуется использовать в классах public поля. Доступ к полям должен всегда контролироваться с использованием соответствующих public методов.

 

Определим наследование следующим образом:

Наследование, inheritance - отношение между классами, при котором класс использует структуру или поведение другого (одиночное наследование) или других (множественное наследование) классов. Наследование вводит иерархию "общее/частное", в которой подкласс наследует от одного или нескольких более общих суперклассов. Подклассы обычно дополняют или переопределяют унаследованную структуру и поведение.

Наследование есть процесс, с помощью которого один объект приобретает свойства другого. Как уже упоминалось в предыдущей лекции, наибольшая часть знаний становится управляемой только с помощью иерархических классификаций. Без применения классификаций каждый объект нуждался бы в явном определении всех своих характеристик. При использовании наследования объект нуждается в определении только тех качеств, которые делают его уникальным в собственном классе. Он может наследовать общие свойства от своего родителя. Родительский класс, на основе которого строится новый класс, называется базовым классом (base class), а потомок – производным классом (derived class).

Базовый класс всегда является боле общим, чем производный. Производный класс использует члены базового класса, но может также изменять и дополнять их. Например, класс «кошки» может наследовать метод «спать» от класса «животные». При этом он может иметь собственную улучшенную версию метода «питаться».

Наследование исключительно важно, так как позволяет объектно-ориентированным программам расти по сложности линейно, а не геометрически. Производный класс наследует описание базового кода, делая ненужным повторную разработку и тестирование кода.

 

Буч определяет полиморфизм так:

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

Концепцию полиморфизма часто выражают фразой «один интерфейс, много методов».

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

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

 

При правильном применении полиморфизм, инкапсуляция и наследование комбинируются так, что создают некую среду программирования, которая обеспечивает намного более устойчивые масштабируемые программы. Удачно спроектированная иерархия классов является базисом для повторного используемого кода. Инкапсуляция позволяет реализациям мигрировать во времени без разрушения кода, который зависит от public-интерфейса классов. Полиморфизм позволяет создавать ясный и читабельный код.

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

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

Люди постоянно взаимодействуют с инкапсулированными свойствами автомобиля. Нажимая на педаль газа, мы обычно не задумываемся о том, какие процессы в автомобиле происходят – нам нет дела до этих процессов. Мы можем и не знать, какие именно части автомобиля при этом оказываются задействованы. Таким образом, педаль тормоза можно считать очень простым интерфейсом взаимодействия автомобиля и водителя.

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

Как можно видеть, именно через применение инкапсуляции, наследования и полиморфизма отдельные части трансформируются в объект, известный как автомобиль. То же самое справедливо и для компьютерных программ. Применяя объектно-ориентированные принципы, разные части сложной программы можно объединить вместе в форму взаимосвязанного, устойчивого и легко обслуживаемого целого.

 

4