Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Наследование и композиция






Автор: Литягин Денис

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

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

Давайте рассмотрим что же такой композиция и как ей пользоваться.

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

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

Именно в тот момент я задумался о том где именно была бы уместной данная концепция объектно-ориентированного программирования. Но ответ пришел уже после. Вспомнив о CakePhp и его поведениях моделей я задумался над понятием композиции.

Итак, давайте рассмотрим простой пример. Предположим, что у нас есть модель базы данных. Она имеет стандартный функционал CRUD и, допустим, мы хотим чтобы эта модель работала с древовидными структурами Nested set. Конечно, мы можем описать все необходимые методы внутри модели и все будет работать. Более того, мы сможем унаследовать от нее другие модели которые тоже смогут работать с такими данными. Но, предположим что появится необходимость, чтобы данная модель вела себя не только как Nested set, но и как словарь таксономии, с возможностью связывания терминов между собой и хранила бы в себе количество материалов, использующих конкретные термины.

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

Смысл его в следующем. Предположим что у нас есть класс A, который хочет реализовать функциональность классов B и C. Для того чтобы такое было возможно используют следующий подход. Возможно, в сравнении с наследованием данная концепция может не показаться столь красивой и удобной, но ее преимущества нельзя недооценивать.

Итак, у нас есть класс A. Он совершенно пуст и не имеет собственных методов и свойств.

class A { }

Также, у нас есть классы B и C, которые имеют некие методы. Для того, чтобы упростить пример, пусть класс B имеет метод printStrong выводящий текст в браузер, обрамляя его в теги < strong>, а у класс C содержит в себе метод printItalic, выводящие текст обрамленный тегом < i>.

class B { public function printStrong($text) { print '< strong> '. $text. '< /strong> '; }} class C { public function printItalic($text) { print '< i> '. $text. '< /i> '; }}

Чтобы к нашему классу A можно было обращаться и как к классу B, и как к классу C дополним его некоторым кодом.

class A { private $bObject; private $cObject; public function __construct() { $this-> bObject = new B(); $this-> cObject = new C(); } public function printStrong($text) { $this-> bObject-> printStrong($text); } public function printItalic($text) { $this-> cObject-> printItalic($text); }}

Как видите, мы не реализовывали заного функционал обоих классов. Все, что мы сделали, это создали в конструкторе класса два объекта классов B и C и присвоили их приватным методам класса A. После чего повторили имена методов обоих классов и делегировали объектам этих классов всю работу. То есть наши методы в классе A по сути являются лишь обертками, позволяющими обращаться к объектам A точно так же как и к объектам классов B и С.

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

Чтобы наглядно показать как это работает создадим интерфейсы для наших класов B и C.

interface BInterface{ public function printStrong($text); } interface CInterface{ public function printItalic($text); }

И дополним наши классы.

class B implements BInterface { public function printStrong($text) { print '< strong> '. $text. '< /strong> '; }} class C implements CInterface { public function printItalic($text) { print '< i> '. $text. '< /i> '; }}

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

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

class A implements BInterface, CInterface{ }

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

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

$aObject = new A(); function test(CInterface $object) { $object-> printItalic('Test string'); } test($aObject);

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

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

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

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

 






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.