ООП - это что такое? Основные принципы объектно-ориентированного программирования. Объектно-ориентированное программирование (ООП) Основными принципами объектно ориентированного программирования являются

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

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

Объектно-ориентированное программирование — это стиль кодирования, который позволяет разработчику группировать схожие задачи в классы . Таким образом код соответствует принципу DRY (don"t repeat yourself - не повторяй самого себя) и становится лёгким для сопровождения.

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

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

Что такое объекты и классы

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

В чем заключается разница между классами и объектами

Разработчики, начиная разговаривать о классах и объектах, начинают подменять понятия. К сожалению, такое очень часто происходит.

Класс, например, это проект дома . Он определяет на бумаге как будет выглядеть дом, чётко описывает все взаимосвязи между его различными частями, даже если дом не существует в реальности.

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

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

Структура класса

Синтаксис для создания класса очень прост: для объявления класса используется ключевое слово class , за которым следует имя класса и набор фигурных скобок ({}):

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

$obj = new MyClass;

Чтобы увидеть содержимое объекта, используйте var_dump() :

Var_dump($obj);

Можно протестировать весь процесс, скопировав весь код в файл test.php:

Загрузите страницу в ваш браузер и на экране должна появиться следующая строка:

Object(MyClass)#1 (0) { }

Вы только что создали свой первый скрипт ООП.

Определение свойств класса

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

Чтобы добавить свойства классу MyClass, используйте такой код в вашем скрипте:

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

Echo $obj->prop1;

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

Модифицируйте скрипт в test.php, чтобы прочитать значение свойства, а не выводить информацию обо всем классе:

prop1; // Выводим свойство?>

Обновите страницу в вашем браузере, чтобы увидеть результат работы скрипта:

Свойство класса

Определение методов класса

Метод — это функция класса. Индивидуальное действие, которое может выполнить объект, определяется в классе как метод.

Например, создадим методы, которые устанавливают и читают значение свойства $prop1:

prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } $obj = new MyClass; echo $obj->prop1; ?>

Примечание — ООП позволяет объекту ссылаться на самого себя, используя $this . При работе внутри метода, использование $this позволяет использовать имя объекта вне класса.

Чтобы использовать метод, вызовите его как обычную функцию, но сначала укажите объект, которому она принадлежит. Читаем свойство из MyClass , изменяем значение, читаем его снова:

prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } $obj = new MyClass; echo $obj->getProperty(); // получаем значение свойства $obj->setProperty("Новое свойство."); // Устанавливаем новое значение echo $obj->getProperty(); // Снова читаем значение, чтобы увидеть изменения?>

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

Свойство класса Новое свойство

Преимущества ООП проявляются при использовании множества объектов одного класса.

prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем два объекта $obj = new MyClass; $obj2 = new MyClass; // Получаем значения $prop1 из обоих объектов echo $obj->getProperty(); echo $obj2->getProperty(); // Устанавливаем новые значения свойств для обоих объектов $obj->setProperty("Новое значение свойства"); $obj2->setProperty("Свойство принадлежит второму объекту"); // Выводим значения $prop1 для обоих echo $obj->getProperty(); echo $obj2->getProperty(); ?>

Когда вы загрузите страницу в браузер, то увидите следующее:

Свойство класса Свойство класса Новое значение свойства Свойство принадлежит второму объекту

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

Магические методы в ООП

Чтобы сделать использование объектов проще, PHP имеет несколько магических методов. Это специальные методы, которые вызываются, когда над объектом производятся определённые действия. Таким образом разработчик может выполнить несколько общих задач относительно просто.

Использование конструкторов и деструкторов

Когда создаётся объект, очень часто нужно, чтобы при этом сразу производились установки некоторых свойств. Для выполнения таких задач PHP имеет магический метод __construct() , который вызывается автоматически при создании нового объекта.

Для иллюстрации концепции, добавим конструктор к классу MyClass. Он будет выводить сообщение при создании нового объекта класса:

"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем новый объект $obj = new MyClass; // Получаем значение свойства $prop1 echo $obj->
"; ?>

Примечание — константа __CLASS__ возвращает имя класса, в котором она вызывается; это одна из магических констант PHP.

Создан объект класса "MyClass"! Свойство класса Конец файла.

Для вызова функции в процессе удаления объекта используется магический метод __destruct() . Это очень полезный метод для корректной очистки свойств класса (например, для правильного закрытия соединения с базой данных).

Выведем сообщение, когда будет удаляться объект класса, с помощью магического метода:
__destruct() in MyClass:

"; } public function __destruct() { echo "Объект класса "", __CLASS__, "" удален.
"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаём новый объект $obj = new MyClass; // Получаем значение свойства $prop1 echo $obj->getProperty(); // Выводим сообщение о достижении конца файла echo "Конец файла.
"; ?>

Обновляем страницу в браузере и получаем результат:

Создан объект класса "MyClass"! Свойство класса Конец файла. Объект класса "MyClass" удален.

При достижении конца файла PHP автоматически освобождает все ресурсы.

Для явного вызова деструктора и удаления объекта Вы можете использовать функцию unset() :


"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем новый объект $obj = new MyClass; // Получаем значение свойства $prop1 echo $obj->getProperty(); // Удаляем объект unset($obj); // Выводим сообщение о достижении конца файла echo "Конец файла.
"; ?>

Теперь результат работы кода будет выглядеть после загрузки в браузер следующим образом:

Создан объект класса "MyClass"! Свойство класса Объект класса "MyClass" удален. Конец файла.

Преобразование в строку

Чтобы избежать ошибки, если скрипт попытается вывести MyClass как строку, используется другой магический метод __toString() .

Без использования __toString() попытка вывести объект как строку приведёт к фатальной ошибке . Попробуйте использовать функцию echo, чтобы вывести объект без применения магического метода:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "

"; ?>

Результат работы будет выглядеть следующим образом:

Создан объект класса "MyClass"! Catchable fatal error: Object of class MyClass could not be converted to string in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 40

Чтобы избежать ошибки, используйте метод __toString() :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем новый объект $obj = new MyClass; // Выводим объект как строку echo $obj; // Удаляем объект unset($obj); // Выводим сообщение о достижении конца файла echo "Конец файла.
"; ?>

В этом случае попытка конвертировать объект в строку приведёт к вызову метода getProperty() . Загружаем скрипт в браузер и смотрим на результат работы:

Создан объект класса "MyClass"! Используем метод toString: Свойство класса Объект класса "MyClass" удален. Конец файла.

Использование наследования классов

Классы могут наследовать методы и свойства от других классов с помощью ключевого слова extends . Например, создадим второй класс, который расширяет возможности MyClass и добавляет метод:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } } // Создаем новый объект $newobj = new MyOtherClass; // Используем новый метод echo $newobj->newMethod(); // Используем метод из родительского класса echo $newobj->getProperty(); ?>

После загрузки скрипта в браузер получим результат:

Создан объект класса "MyClass"! Из нового метода класса "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Перегрузка унаследованных свойств и методов

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

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { echo "Новый конструктор в классе " . __CLASS__ . ".

"; } } // Создаем новый объект $newobj = new MyOtherClass; // Выводим объект как строку echo $newobj->newMethod(); // Используем метод из родительского класса echo $newobj->getProperty(); ?>

Изменения приведут к следующему выводу при выполнении кода:

Новый конструктор в классе "MyOtherClass". Из нового метода класса "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Сохранение оригинальной функциональности при перегрузке методов

Чтобы добавить новую функциональность к унаследованному методу при сохранении функций оригинального метода, используйте ключевое слово parent с оператором разрешения видимости ( :: ) :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); // Вызываем конструктор родительского класса echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } } // Создаем новый объект $newobj = new MyOtherClass; // Выводим объект как строку echo $newobj->newMethod(); // Используем метод из родительского класса echo $newobj->getProperty(); ?>

Выше приведённый код при выполнении выведет на экран сообщения из обеих конструкторов нового и родительского класса:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Из нового метода класса "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Определение области видимости свойств и методов

Для дополнительного контроля над объектами, методами и свойствами устанавливается область видимости. Таким образом контролируется как и откуда могут быть доступны свойства и методы. Существует три ключевых слова для установки области видимости: public , protected , и private . В дополнение к установке области видимости, методы и свойства могут быть объявлены как static , что позволяет получать к ним доступ без реализации класса.

Примечание — Область видимости — это новое свойство, которое было введено в PHP 5. чтобы узнать о совместимости ООП с PHP 4 , смотрите руководство по использованию PHP.

Свойства и методы public (Общие)

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

Методы и свойства protected (Защищённые)

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

Объявим метод getProperty()как protected в MyClass и попробуем получить доступ к нему вне класса:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } protected function getProperty() { return $this->prop1 . "

"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } } // Создаем новый объект $newobj = new MyOtherClass; // Пробуем вызвать защищенный метод echo $newobj->getProperty(); ?>

При попытке выполнить скрипт, будет сгенерирована следующая ошибка:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Fatal error: Call to protected method MyClass::getProperty() from context "" in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 55

Теперь, создадим новый метод в MyOtherClass для вызова метода getProperty() :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } protected function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } public function callProtected() { return $this->getProperty(); } } // Создаём новый объект $newobj = new MyOtherClass; // Вызываем защищённый метод из общего метода echo $newobj->callProtected(); ?>

При выполнении скрипта результат будет таким:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Методы и свойства private (Частные)

Свойства и методы, объявленные с директивой private, доступны только внутри класса, в котором они определены . Это означает, что даже если новый класс будет производным от класса, в котором определены частные свойства и методы, они не будут доступны в производном классе.

Для демонстрации объявим метод getProperty() как private в MyClass , и попробуем вызвать метод callProtected() из
MyOtherClass :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } private function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } public function callProtected() { return $this->getProperty(); } } // Создаём новый объект $newobj = new MyOtherClass; // Используем метод из родительского класса echo $newobj->callProtected(); ?>

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

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Fatal error: Call to private method MyClass::getProperty() from context "MyOtherClass" in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 49

Методы и свойства Static (статические)

Методы и свойства, объявленные с директивой static могут быть доступны без инициации класса. Вы просто используете имя класса, оператор разрешения видимости и имя свойства или метода.

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

Для демонстрации добавим статическое свойство $count и статический метод plusOne() к классу MyClass . Затем установим цикл do...while для вывода увеличивающего значения $count, до тех пор пока оно не станет больше 10:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } private function getProperty() { return $this->prop1 . "
"; } public static function plusOne() { return "count = " . ++self::$count . ".
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } public function callProtected() { return $this->getProperty(); } } do { // Вызываем plusOne без инициации класса MyClass echo MyClass::plusOne(); } while (MyClass::$count < 10); ?>

Примечание — Для доступа к статическим свойствам знак доллара ($) должен стоять после оператора разрешения видимости.

Выполнение скрипта в браузере выдаст следующий результат:

Count = 1. count = 2. count = 3. count = 4. count = 5. count = 6. count = 7. count = 8. count = 9. count = 10.

Абстрактные типы данных

Понятие абстрактных типов дан­ных является ключевым в программировании. Абстракция подразумевает разделение и независимое рассмотрение ин­терфейса и реализации.

Рассмотрим пример. Все мы смотрим телевизионные про­граммы. Назовем телевизор модулем или объектом. Этот объект имеет интерфейс с пользователем, т. е. средства управ­ления (совокупность кнопок), воспроизведения изображения и звука. Чем совершеннее интерфейс, тем удобнее телевизор в использовании. Мы переключаем программы, нажимая опре­деленные кнопки, и при этом не задумываемся о физических процессах, происходящих в телевизоре. Об этом знают спе­циалисты. Когда мы выбираем телевизор, нас интересуют его цена и эксплуатационные параметры, т. е. качество изобра­жения, звука и т. п. Однако нас не интересует то, что находит­ся внутри. Другими словами, мы возвращаемся к свойствам объекта (модуля), какими являются интерфейс и реализация. Основная цель абстракции в программировании как раз и заключается в отделении интерфейса от реализации.

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

Абстракция данных предполагает определение и рассмот­рение абстрактных типовданных (АТД) или, что то же самое, новых типов данных, введенных пользователем.

Абстрактный тип данных - это совокупность данных вместе с множеством операций, которые можно выполнять над этими данными.

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

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

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

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

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

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

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

ОО-программирование является, несомненно, одним из наиболее интересных направлений для профессиональной разработки программ.

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

Базовыми блоками объектно-ориентированной програм­мы являются объекты и классы. Содержательно объект мож­но представить как что-то ощущаемое или воображаемое и имеющее хорошо определенное поведение. Таким образом, объект можно либо увидеть, либо потрогать, либо, по край­ней мере, знать, что он есть, например, представлен в виде информации, хранимой в памяти компьютера. Дадим определение объекта, придерживаясь мнения Гради Буча: «Объект – осязаемая сущность, которая четко проявляет свое поведение».

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

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

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

Определим теперь понятия состояния, поведения и иденти­фикации объекта.

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

Поведение выражает динамику изменения состояний объ­екта и его реакцию на поступающие сообщения, т.е. как объект изменяет свои состояния и взаи­модействует с другими объектами.

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

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

Базовые принципы ООП

К базовым принципам объектно-ориентированного стиля программирования относятся:

  • пакетирование или инкапсуляция;
  • наследование;
  • полиморфизм;
  • передача сообщений.

Пакетирование (инкапсуляция)

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

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

В рамках ООП данные называются полями объекта, а алгоритмы – объектными методами.

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

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

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

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

По Гради Бучу «наследование – это такое отношение между объектами, когда один объект повторяет структуру и поведение другого».

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

Типы верхних уровней объектно-ориентированной иерархии, как правило, не имеют конкретных экземпляров объектов. Не существует, например, конкретного живого организма, который бы сам по себе назывался «млекопитающее» или «птица». Такие типы называют абстрактными. Конкретные экземпляры объектов имеют, как правило, типы самых нижних уровней ОО-иерархии: «крокодил Гена» – конкретный экземпляр объекта типа «крокодил», «кот Матроскин» – конкретный экземпляр объекта типа «кошка».

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

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

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

Когда вы строите новый класс, наследуя его из сущест­вующего класса, можно:

  • добавить в новый класс новые компоненты-данные;
  • добавить в новый класс новые компоненты-функции;
  • заменить в новом классе наследуемые из старого класса компоненты-функции.

Полиморфизм

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

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

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

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

Таким образом, в нашем примере с объектами-животными действие «бежать» будет называться полиморфическим действием, а многообразие форм проявления этого действия – полиморфизмом.

Описание объектного типа

Класс или объект – это структура данных, которая содержит поля и методы. Как всякая структура данных она начинается зарезервированным словом и закрывается оператором end . Формальный синтаксис не сложен: описание объектного типа получается, если в описании записи заменить слово record на слово object или class и добавить объявление функций и процедур над полями.

Type <имя типа объекта>= object
<поле>;
<поле>;
….
<метод>;
<метод>;
end ;

В ObjectPascal существует специальное зарезервированное слово class для описания объектов, заимствованное из С++.

Type <имя типа объекта>= class
<поле>;
….
<метод>;
<метод>;
end ;

ObjectPascal поддерживает обе модели описания объектов.

Компонент объекта – либо поле, либо метод. Поле содержит имя и тип данных. Метод – это процедура или функция, объявленная внутри декларации объектного типа, в том числе и особые процедуры, создающие и уничтожающие объекты (конструкторы и деструкторы). Объявление метода внутри описания объектного типа состоит только из заголовка. Это разновидность предварительного описания подпрограммы. Тело метода приводится вслед за объявлением объектного типа.

Пример . Вводится объектный тип «предок», который имеет поле данных Name (имя) и может выполнять два действия:

  • провозглашать: «Я – предок!»;
  • сообщать свое имя.

Type tPredoc = object Name: string ; {поле данных объекта}
Procedure Declaration ; {объявление методов объекта}
Procedure MyName ;
End ;

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

Procedure tPredoc.Declaration ; {реализация метода объекта}
begin
writeln ("Я – предок!");
end ;
Procedure tPredoc.MyName ; {реализация метода объекта}
begin
writeln("Я –", Name);
end;

Внутри описания методов на поля и методы данного типа ссылаются просто по имени. Так метод MyName использует поле Name без явного указания его принадлежности объекту так, если бы выполнялся неявный оператор with <переменная_типа_объект> do .

Под объектами понимают и переменные объектного типа – их называют экземплярами . Как всякая переменная, экземпляр имеет имя и тип: их надо объявить.

…….{объявление объектного типа и описание его методов}
var v 1: tPredoc ; {объявление экземпляра объекта}
begin
v1. Name:= "Петров Николай Иванович";
v1.Declaration;
v1.MyName
end.

Использование поля данных объекта v1 не отличается по своему синтаксису от использования полей записей. Вызов методов экземпляра объекта означает, что указанный метод вызывается с данными объекта v 1. В результате на экран будут выведены строчки

Я – предок!
Я – Петров Николай Иванович

Аналогично записям, к полям переменных объектного типа разрешается обращаться как с помощью уточненных идентификаторов, так и с помощью оператора with .

Например, в тексте программы вместо операторов

возможно использование оператора with такого вида

with v1 do
begin
Name:= "Петров Николай Иванович";
Declaration ;
MyName
End ;

Более того, применение оператора with с объектными типами, также как и для записей не только возможно, но и рекомендуется.

Иерархия типов (наследование)

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

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

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

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

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

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

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

Наследование дочерними типами информационных полей и методов их родительских типов выполняется по следующим правилам.

Правило 1 . Информационные поля и методы родительского типа наследуются всеми его дочерними типами независимо от числа промежуточных уровней иерархии.

Правило 2 . Доступ к полям и методам родительских типов в рамках описания любых дочерних типов выполняется так, как будто-бы они описаны в самом дочернем типе.

Правило 3 . Ни в одном дочернем типе не могут быть использованы идентификаторы полей родительских типов.

Правило 4 . Дочерний тип может доопределить произвольное число собственных методов и информационных полей.

Правило 5 . Любое изменение текста в родительском методе автоматически оказывает влияние на все методы порожденных дочерних типов, которые его вызывают.

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

Продолжим рассмотрение нашего примера. В дополнение к введенному нами типу предка tPredoc можно ввести типы потомков:

tуре tSon= оbject(tPredoc) {Тип, наследующий tPredoc }
procedure Declaration; {перекрытие методов предка}
procedure Му Name(Predoc: tPredoc);
end ;

Tуре tGrandSon=object(tSon) {Тип, наследующий tSon}
procedure Declaration ; {перекрытие методов предка}
end ;

Имя типа предка приводится в скобках после слова оbject. Мы породили наследственную иерархию из трех типов: tSon («сын») наследник типу tPredoc , а тип tGrandSon (“внук”) ­- типу tSon. Тип tSon переопределяет методы Declaration и Му N а m е, но наследует поле Name . Тип tGrandSon переопределяет только метод Declaration и наследует от общего предка поле Name , а от своего непосредственного предка (типа tSon) переопределенный метод Declaration .

Давайте разберемся, что именно мы хотим изменить в родительских методах. Дело в том, что «сын» должен провозглашать несколько иначе, чем его предок, а именно сообщить "Я – отец!"

procedure tSon.Declaration ; {реализация методов объектов - потомков}
begin
writeln (" Я - отец!");
end;

А называя свое имя, “сын” должен сообщить следующие сведения:

  • Я <фамилия имя отчество >
  • Я – сын <фамилия имя отчество своего предка>

procedure tSon .Му Name (predoc: tPredoc);
begin
inherited Му Name ; {вызов метода непосредственного предка}
writeln ("Я - сын ", predoc.Name, " а ");
end;

В нашем примере потомок tSon из метода Му Name вызывает одноимен­ный метод непосредственного предка типа tPredoc . Такой вызов обес­печивается директивой inherited , после которой указан вызываемый метод непосредственного предка. Если возникает необходимость вызвать метод отдаленного предка в каком-нибудь дочернем типе на любом уровне иерархии, то это можно сделать с помощью уточненного идентификатора, т.е. указать явно имя типа родительского объекта и через точку – имя его метода:

Теперь давайте разберемся с «внуком». Метод, в котором «внук» называет свое имя, в точности такой же, как и у его непосредственного предка (типа tSon), поэтому нет необходимости этот метод переопределять, этот метод лучше автоматически наследовать и пользоваться им как своим собственным. А вот в методе Declaration нужно провозгласить "Я – внук!", поэтому метод придется переопределить.

procedure tGrandSon.Declaration;
begin
writeln (" Я - внук!");
end;

Рассмотрим пример программы, в которой определим экземпляр типа tPredoc , назовем его «дед», экземпляр типа tSon – «отец», и экземпляр типа tGrandSon – «внук». Потребуем от них, чтобы они представились.

Пример программы с испльзованием ООП

{заголовок программы}
……………….
{раздел описания типов, в том числе и объектных типов tPredoc , tSon , tGrandSon }
{Обратите внимание! Экземпляры объектных типов можно описать как типизированные константы, что мы для примера и сделали ниже}
const ded: tPredoc = (Name: "Петров Николай Иванович");
otec: tSon = (Name: "Петров Сергей Николаевич");
vnuk: tGrandSon = (Name: "Петров Олег Сергеевич");
{раздел описания процедур и функций, где обязательно должны быть написаны все объявленные в объектных типах методы}
begin
ded.Declaration ; {вызов методов общего предка}
ded.Му Name;
writeln;
otec.Declaration;
otec.MyName(ded); { вызов методов объекта otec типа tSon}
writeln;
vnuk.Declaration; { вызов методов объекта vnuk типа tGrandSon}
vnuk.MyName (otec);
end .

Наша программа выведет на экран:

Пример вывода на экран результата

Я -предок!
Я -Петров Николай Иванович

Я -отец!
Я -Петров Сергей Николаевич
Я -сын Петров Николай Ивановича

Я -внук!
Я -Петров Олег Сергеевич
Я -сын Петров Сергей Николаевича

Обратите внимание, что в заголовке процедуры tSon . MyName в качестве параметра приведен тип данных tPredoc , а при использовании этой процедуры ей передаются переменные как типа tPredoc , так и типа tSon . Это возможно, так как пре­док совместим по типу со своими потомками. Обратное несправедливо. Если мы заменим в заголовке процедуры tSon . MyName при описании параметров тип tPredoc на tSon , компилятор укажет на несовместимость типов при использовании перемен­ной ded в строке otec . MyName (ded).

Полиморфизм и виртуальные методы

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

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

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

В рассмотренном выше примере во всех трех объектных типах tPredoc , tSon и tGrandSon действуют одноименные методы Declaration и MyName . Но в объектном типе tSon метод MyName выполняется несколько иначе, чем у его предка. А все три одноименных метода Declaration для каждого объекта выполняются по-своему.

Методы объектов бывают статическими, виртуальными и динамическими.

Статические методы

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

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

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

Виртуальные методы

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

Этот принципиально новый механизм, называемый поздним связыванием, обеспечивает полиморфизм, т.е. разный способ поведения для разных, но однородных (в смысле наследования) объектов.

Описание виртуального метода отличается от описания обычного метода добавлением после заголовка метода служебного слова virtual .

procedure Method (список параметров); virtual;

Использование виртуальных методов в иерархии типов объектов имеет определенные ограничения:

  • если метод объявлен как виртуальный, то в типе потомка его нельзя перекрыть статическим методом;
  • объекты, имеющие виртуальные методы, инициализируются специальными процедурами, которые, в сущности, также являются виртуальными и носят название constructor ;
  • списки переменных, типы функций в заголовках перекрывающих друг друга виртуальных процедур и функций должны совпадать полностью;

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

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

Конструктор – это специальный метод, который инициализирует объект, содержащий виртуальные методы. Заголовок конструктора выглядит так:

constructor Method (список параметров);

Зарезервированное слово constructor заменяет слова procedure и virtual .

Основное и особенное назначение конструктора – установление связей с таблицей виртуальных методов (VMT) – структурой, содержащей ссылки на виртуальные методы. Таким образом, конструктор инициализирует объект установкой связи между объектом и VMT с адресами кодов виртуальных методов. При инициализации и происходит позднее связывание.

У каждого объекта своя таблица виртуальных методов VMT . Именно это и позволяет одноименному методу вызывать различные процедуры.

Упомянув о конструкторе, следует сказать и о деструкторе . Его роль противоположна: выполнить действия, завершающие работу с объектом, закрыть все файлы, очистить динамическую память, очистить экран и т.д.

Заголовок деструктора выглядит таким образом:

destructor Done ;

Основное назначение деструкторов – уничтожение VMT данного объекта. Часто деструктор не выполняет других действий и представляет собой пустую процедуру.

destructor Done ;
begin end ;

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

Любая программа, написанная на языке ООП, отражает в своих данных состояние физических предметов либо абстрактных понятий – объектов программирования, для работы, с которыми она предназначена.

Все данные об объекте программирования и его связях с другими объектами можно объединить в одну структурированную переменную. В первом приближении ее можно назвать объектом.

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

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

Класс – это описание множества объектов программирования (объектов) и выполняемых над ними действий.

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

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

Любая функция в программе представляет собой метод для объекта некоторого класса.

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

Вся программа в таком виде представляет собой объект некоторого класса с единственным методом run (выполнить).

Программирование «от класса к классу» включает в себя ряд новых понятий. Основными понятиями ООП являются

  • инкапсуляция;
  • наследование;
  • полиморфизм.

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

Внутри объекта коды и данные могут быть закрытыми или открытыми.

Закрытые коды или данные доступны только для других частей того же самого объекта и, соответственно, недоступны для тех частей программы, которые существуют вне объекта.

Открытые коды и данные, напротив, доступны для всех частей программы, в том числе и для других частей того же самого объекта.

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

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

Иначе говоря, новый класс наследует как данные старого класса, так и методы их обработки.

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

Пример наследования – определение структуры, отдельный член которой является ранее определенной структурой.

Полиморфизм – это свойство, которое позволяет один и тот же идентификатор (одно и то же имя) использовать для решения двух и более схожих, но технически разных задач.

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

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

Например, нахождение абсолютной величины в языке Си требует трех разных функций с разными именами.

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

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

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

В этом параграфе мы продолжим знакомство с базисными концепциями объектно-ориентированного программирования, начатое еще в первой главе книги. Сначала будут обсуждены общие для различных языков программирования понятия ООП , а затем - их реализация в языке Java .

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

Основные концепции ООП

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

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

ООП характеризуется следующими принципами ( по Алану Кею):

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

Определение 10.1 . Абстрагирование (abstraction) - метод решения задачи, при котором объекты разного рода объединяются общим понятием (концепцией), а затем сгруппированные сущности рассматриваются как элементы единой категории.

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

Определение 10.2 . Инкапсуляция (encapsulation) - техника, при которой несущественная с точки зрения интерфейса объекта информация прячется внутри него.

Определение 10.3 . Наследование (inheritance) - свойство объектов, посредством которого экземпляры класса получают доступ к данным и методам классов-предков без их повторного определения.

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

Определение 10.4 .

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

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

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

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

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

  • Класс – тип данных, описывающий структуру и поведение объектов.
  • Объект – экземпляр класса.
  • Поле – элемент данных класса: переменная элементарного типа, структура или другой класс, являющийся частью класса.
  • Состояние объекта – набор текущих значений полей объекта.
  • Метод – процедура или функция, выполняющаяся в контексте объекта, для которого она вызывается. Методы могут изменять состояние текущего объекта или состояния объектов, передаваемых им в качества параметров.
  • Свойство – специальный вид методов, предназначенный для модификации отдельных полей объекта. Имена свойств обычно совпадают с именами соответствующих полей. Внешне работа со свойствами выглядит точно так же, как работа с полями структуры или класса, но на самом деле перед тем, как вернуть или присвоить новое значение полю может быть выполнен программный код, осуществляющий разного рода проверки, к примеру, проверку на допустимость нового значения.
  • Член класса – поля, методы и свойства класса.
  • Модификатор доступа – дополнительная характеристика членов класса, определяющая, имеется ли к ним доступ из внешней программы, или же они используются исключительно в границах класса и скрыты от окружающего мира. Модификаторы доступа разделяют все элементы класса на детали реализации и открытый или частично открытый интерфейс.
  • Конструктор – специальный метод, выполняемый сразу же после создания экземпляра класса. Конструктор инициализирует поля объекта – приводит объект в начальное состояние. Конструкторы могут быть как с параметрами, так и без. Конструктор без параметров называют конструктором по умолчанию, который может быть только один. Имя метода конструктора, чаще всего, совпадает с именем самого класса.
  • Деструктор – специальный метод, вызываемый средой исполнения программы в момент, когда объект удаляется из оперативной памяти. Деструктор используется в тех случаях, когда в состав класса входят ресурсы, требующие явного освобождения (файлы, соединения с базами данных, сетевые соединения и т.п.)
  • Интерфейс – набор методов и свойств объекта, находящихся в открытом доступе и призванных решать определенный круг задач, к примеру, интерфейс формирования графического представления объекта на экране или интерфейс сохранения состояния объекта в файле или базе данных.
  • Статический член – любой элемент класса, который может быть использован без создания соответствующего объекта. К примеру, если метод класса не использует ни одного поля, а работает исключительно с переданными ему параметрами, то ничто не мешает его использовать в контексте всего класса, не создавая отдельных его экземпляров. Константы в контексте класса обычно всегда являются статическими его членами.

Преимущества объектно-ориентированного программирования

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

  • Инкапсуляция обозначает сокрытие деталей реализации классов средствами награждения отдельных его членов соответствующими модификаторами доступа. Таким образом, вся функциональность объекта, нацеленная на взаимодействие с другими объектами программы группируется в открытый интерфейс, а детали тщательно скрываются внутри, что избавляет основной код бизнес-логики информационной системы от ненужных ему вещей. Инкапсуляция повышает надежность работы программного кода, поскольку гарантирует, что определенные данные не могут быть изменены за пределами содержащего их класса.
  • Наследование . Краеугольный камень ООП. В объектно-ориентированном программировании есть возможность наследовать структуру и поведение класса от другого класса. Класс, от которого наследуют, называется базовым или суперклассом, а класс, который получается вследствие наследования – производным или просто потомком. Любой класс может выступать как в роли суперкласса, так и в роли потомка. Связи наследования классов образуют иерархию классов. Множественным наследованием называют определение производного класса сразу от нескольких суперклассов. Не все объектно-ориентированные языки программирования поддерживают множественное наследование. Наследование – это эффективный способ выделения многократно используемых фрагментов кода, но у него есть и минусы, о которых будет рассказано далее.
  • Абстрагирование . Возможность объединять классы в отдельные группы, выделяя общие, значимые для них всех характеристики (общие поля и общее поведение). Собственно, абстрагирование и есть следствие наследования: базовые классы не всегда имеют свою проекцию на объекты реального мира, а создаются исключительно с целью выделить общие черты целой группы объектов. К примеру, объект мебель – это базовое понятие для стола, стула и дивана, всех их объединяет то, что это движимое имущество, часть интерьера помещений, и они могут быть выполнены для дома или офиса, а также относиться к “эконом” или “премиум” классу. В ООП есть для этого отдельное понятие абстрактный класс – класс, объекты которого создавать запрещено, но можно использовать в качестве базового класса. Наследование и абстрагирование позволяют описывать структуры данных программы и связи между ними точно так же, как выглядят соответствующие им объекты в рассматриваемой модели предметной области.

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

Диаграмма классов или иерархия наследования "Транспортные средства". Белые квадраты обозначают абстрактные классы.

  • Полиморфизм . Еще одно свойство, которое является следствием наследования. Дело в том, что объектно-ориентированные языки программирования позволяют работать с набором объектов из одной иерархии точно так же, как если бы все они были объектами их базового класса. Если вернуться к примеру про мебель, то можно предположить, что в контексте создания информационной системы для мебельного магазина в базовый класс для всех видов мебели разумно добавить общий для всех метод “показать характеристики”. При распечатке характеристик всех видов товара программа бы без разбору для всех объектов вызывала бы этот метод, а каждый конкретный объект уже сам бы решал, какую информацию ему предоставлять. Как это реализуется: Во-первых, в базовом классе определяют общий для всех метод с общим для всех поведением. В случае с нашим примером это будет метод, печатающий общие для любых типов мебели параметры. Во-вторых, в каждом производном классе, где это необходимо, переопределяют базовый метод (добавляют метод с тем же именем), где расширяют базовое поведение своим, например, выводят характеристики, свойственные только конкретному виду мебельной продукции. Метод в базовом классе иногда вообще не обязан содержать какой-либо код, а необходим только для того, чтобы определить имя и набор параметров – сигнатуру метода. Такие методы называют абстрактными методами, а классы, их содержащие, автоматически становятся абстрактными классами. Итак, полиморфизм – это возможность единообразного общения с объектами разных классов через определенный интерфейс. Идеология полиморфизма гласит, что для общения с объектом вам не нужно знать его тип, а нужно знать, какой интерфейс он поддерживает.
  • Интерфейс . В некоторых языках программирования (C#, Java) понятие интерфейса выделено явно - это не только открытые методы и свойства самого класса. Такие языки, как правило, не поддерживают множественного наследования и компенсируют это тем, что любой объект может иметь один базовый объект и реализовывать любое количество интерфейсов. Интерфейс в их интерпретации – это подобие абстрактного класса, содержащего только описание (сигнатуру) открытых методов и свойств. Реализация интерфейса ложится на плечи каждого класса, который собирается его поддерживать. Один и тот же интерфейс могут реализовывать классы абсолютно разных иерархий, что расширяет возможности полиморфизма. К примеру, интерфейс “сохранение/восстановление информации в базе данных” могли бы реализовывать как классы иерархии “мебель”, так и классы, связанные с оформлением заказов на изготовление мебели, а при нажатии на кнопку “сохранить” программа бы прошлась по всем объектами, запросила бы у них этот интерфейс и вызвала бы соответствующий метод.

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

Некоторые элементы современного объектно-ориентированного программирования

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

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

Недостатки объектно-ориентированного программирования

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

1. ООП порождает огромные иерархии классов, что приводит к тому, что функциональность расползается или, как говорят, размывается по базовым и производным членам класса, и отследить логику работы того или иного метода становится сложно.

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

3. Также, на скорости выполнения программ может неблагоприятно сказаться реализация полиморфизма, которая основана на механизмах позднего связывания вызова метода с конкретной его реализацией в одном из производных классов.

4. Психологический аспект. Многие считают, что ООП это круто и начинают использовать его подходы всегда и везде и без разбору. Все это приводит к снижению производительности программ в частности и дискредитации ООП в целом.