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

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

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

Основным понятием ООП является объект или класс в C++, который можно рас­сматривать с двух позиций. Во-первых, с позиции предметной области: класс соответ­ствует определенному характерному объекту этой области. Во-вторых, с позиции технологии программирования, реализующей это соответствие: «класс» в ООП - это определенная программная структура, которая обладает тремя важней­шими свойствами:

- инкапсуляции;

- наследования;

- полиморфизма.

Эти свойства используются программистом, а обеспечиваются объектно-ориен­тированным языком программирования (транслятором, реализующим это язык). Они позволяют адекватно отражать структуру предметной области.


Объекты и классы.

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

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

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

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

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


Инкапсуляция свойств объектов.

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

В C++ данные класса и объекта называются элементами данных или полями, а функции — методами или элементами-функциями.

Доступ к полям и методам объекта осуществляется через имя объекта и соот­ветствующие имена полей и методов при помощи операций выбора «.» и «->». Это позволяет в максимальной степени изолировать содержание объекта от внеш­него окружения, т. е. ограничить и наглядно контролировать доступ к элементам объекта. В результате замена или модификация полей и методов, инкапсулиро­ванных в объект, как правило, не влечет за собой плохо контролируемых послед­ствий для программы в целом. При необходимости указания имени объекта в теле описания этого объекта в C++ используется зарезервированное слово this,, которое в рамках объекта является специальным синонимом имени объекта - указателем на объект.

Зачем нужна инкапсуляция? Ответ прост, мы - люди. А человеку свойственно ошибаться. Никто не застрахован от ошибок. Применяя инкапсуляцию, мы, как бы, возводим крепость, которая защищает данные, принадлежащие объекту, от возможных ошибок, которые могут возникнуть при прямом доступе к этим данным. Кроме того, применение этого принципа очень часто помогает локализовать возможные ошибки в коде программы. А это на много упрощает процесс поиска и исправления этих ошибок.

Можно сказать, что инкапсуляция подразумевает под собой скрытие данных (data hiding), что позволяет защитить эти данные.

А теперь определение, которое точно определяет суть инкапсуляции:

Переменные состояния объекта скрыты от внешнего мира. Изменение состояния объекта(его переменных) возможно ТОЛЬКО с помощью его методов(операций).

Почему же это так важно? Этот принцип позволяет защитить переменные состояния объекта от неправильного их использования.

Это существенно ограничивает возможность введения объекта в недопустимое состояние и несанкционированное разрушение этого объекта.

Для иллюстрации приведенного выше постулата рассмотрим пример.

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

Для того, чтобы починить машину не причинив себе и самой машине вреда необходимо пригласить квалифицированных авто-слесарей, причем каждый из которых отлично разбирается только в определенной части Вашей машины. Если Вы скажете, что у Вас не горит лампочка подсветки в салоне, то замену лампочки проведет специалист по электрооборудованию автомобилей. Аналогично и в нашем объекте. Есть "мастера" - методы, которые "специализируются" в определенных областях, но свою область они знают. А самое главное, они знают как можно изменить состояние объекта так, чтобы не повредить его. Описанный постулат отражает простую житейскую мудрость: не знаешь, не представляешь как что-то сделать - попроси это сделать того, кто знает как это правильно надо сделать. К сожалению, все мы на каждом шагу пренебрегаем этим правилом. В ООП мы это правило определяем как закон: "Объект не приемлет дилетантов. Только специалисты могут как-либо изменять состояние объекта." Вы можете сказать, что этот принцип далеко не новость в программировании.

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

Для расширения доступа к элементам данных, имеющим атрибуты private или protected, в классе можно реализовать с атрибутом public специальные методы досту­па к собственным и защищенным элементам данных.

Методы в классе могут быть объявлены как дружественные (friend) или виртуальные (virtual). Иногда встречается объявление перегружаемых (overload) функций.

Гибкое разграничение доступа позволяет уменьшить нежелательные (бесконт­рольные) искажения свойств объекта или несанкционированное использование свойств классов.

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

Наследование свойств.

Наследование есть свойство классов порождать своих потомков и наследо­вать свойства (элементы данных и методы) своих родителей. Класс-потомок автоматически наследует от родителя все элементы данных и методы, а также может содержать новые элементы данных и методы и даже заменять (перекры­вать, переопределять) методы родителя или модифицировать (дополнять) их.

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

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

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

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

Наследование в ООП позволяет адекватно отражать родственные отношения объектов предметной области. Если класс В обладает всеми свойствами клас­са А и еще имеет дополнительные свойства, то класс А называется базовым (родительским), а класс В называется наследником класса А. В C++ возможно одиночное (с одним родителем) и множественное (с несколькими родителями) наследование.

Родственные отношения или отношения включения свойств классов, мо­гут отражаться не только с помощью наследования, но и путем инкапсуляции в классе в качестве элементов данных других классов.

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

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

Примеры родственных классов: Координаты на экране -> Цветная точка -> Пря­мая -> Прямоугольник. Здесь направление стрелки указывает порядок наследования свойств классов.

При указании базового (родительского) класса в описании класса в С++ требуется указать ключевое слово public. Указание этого ключевого слова позволит получить свободный доступ ко всем методам класса, как если бы они были описаны в самом производном классе. В противном же случае, мы не сможем получить доступ к методам родительского класса.

Пример описания наследования классов на С++:

class A

{

.....

}

class B: public A

{

.....

}


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: