Я ищу хороший, чистый способ обойти тот факт, что PHP5 по-прежнему не поддерживает множественное наследование. Вот иерархия классов:
Сообщение
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage
У двух типов классов Invitation * много общего; Я бы хотел иметь общий родительский класс, Приглашение, от которого они оба унаследовали бы. К сожалению, у них также много общего со своими нынешними предками ... TextMessage и EmailMessage. Здесь классическая тяга к множественному наследованию.
Какой самый легкий подход к решению проблемы?
Спасибо!
@ OndřejMirtes, что вы имеете в виду - «не так много случаев, когда наследование оправдано»?
Я имею в виду - наследование приносит больше проблем, чем пользы (посмотрите на принцип подстановки Лискова). С композицией можно решить практически все и избавиться от головной боли. Наследование также статично - это означает, что вы не можете изменить то, что уже написано в коде. Но композицию можно использовать во время выполнения, и вы можете динамически выбирать реализации - e. грамм. повторно использовать один и тот же класс с разными механизмами кэширования.
У PHP 5.4 есть "особенности": stackoverflow.com/a/13966131/492130
@ OndřejMirtes: LSP по своей сути не конфликтует с множественным наследованием. Например, Triangle вполне может быть Finite (с функцией boundingRectangle), а также Intersectable (с функцией intersects), вообще не мешая LSP.
Новичкам предлагаю никогда не используйте наследование. В общем, только две ситуации, в которых разрешено наследование: 1) при создании библиотеки, поэтому пользователи пишут меньше кода, и 2) когда руководитель проекта требует, чтобы вы использовали ее.






Похоже, что декоратор шаблон может быть подходящим, но трудно сказать без дополнительных деталей.
Лучший ответ ИМО.
У фреймворка Symfony есть плагин mixin для этого, вы можете проверить его - даже просто для идей, если не использовать его.
Ответ «паттерна проектирования» состоит в том, чтобы выделить общие функциональные возможности в отдельный компонент и составить их во время выполнения. Подумайте о способе абстрагироваться от функциональности приглашения как класса, который связан с вашими классами сообщений каким-либо образом, кроме наследования.
Та же проблема, что и у Java. Попробуйте использовать интерфейсы с абстрактными функциями для решения этой проблемы.
Может быть, вы можете заменить отношение «есть-а» на отношение «имеет-а»? Приглашение может содержать сообщение, но не обязательно, чтобы оно было «есть». Приглашение, например. может быть подтверждено, что не очень хорошо сочетается с моделью сообщения.
Поищите «композиция против наследования», если вам нужно узнать об этом больше.
PHP поддерживает интерфейсы. Это может быть хорошей ставкой, в зависимости от ваших сценариев использования.
Интерфейсы не позволяют реализовывать конкретные функции, поэтому здесь они бесполезны.
Интерфейсы действительно поддерживают множественное наследование, в отличие от классов.
Алекс, в большинстве случаев множественное наследование является сигналом, что структура вашего объекта несколько неверна. В описанной вами ситуации я вижу, что у вас слишком широкая классовая ответственность. Если сообщение является частью бизнес-модели приложения, оно не должно заботиться о рендеринге вывода. Вместо этого вы можете разделить ответственность и использовать MessageDispatcher, который отправляет сообщение, переданное с помощью текстового или HTML-бэкэнда. Я не знаю вашего кода, но позвольте мне смоделировать его следующим образом:
$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <[email protected]>';
$m->to = 'Random Hacker <[email protected]>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');
$d = new MessageDispatcher();
$d->dispatch($m);
Таким образом, вы можете добавить некоторую специализацию к классу Message:
$htmlIM = new InvitationHTMLMessage(); // html type, subject and body configuration in constructor
$textIM = new InvitationTextMessage(); // text type, subject and body configuration in constructor
$d = new MessageDispatcher();
$d->dispatch($htmlIM);
$d->dispatch($textIM);
Обратите внимание, что MessageDispatcher примет решение, отправлять ли в формате HTML или в виде обычного текста, в зависимости от свойства type в переданном объекте сообщения.
// in MessageDispatcher class
public function dispatch(Message $m) {
if ($m->type == 'text/plain') {
$this->sendAsText($m);
} elseif ($m->type == 'text/html') {
$this->sendAsHTML($m);
} else {
throw new Exception("MIME type {$m->type} not supported");
}
}
Подводя итог, можно сказать, что ответственность разделена между двумя классами. Конфигурация сообщения выполняется в классе InvitationHTMLMessage / InvitationTextMessage, а алгоритм отправки делегируется диспетчеру. Это называется паттерном стратегии, вы можете прочитать о нем подробнее здесь.
Невероятно обширный ответ, спасибо! Я кое-что узнал сегодня!
... Я знаю, что это немного устарело (я искал, есть ли у PHP MI ... просто из любопытства). Я не думаю, что это хороший пример шаблона стратегии. Шаблон стратегии разработан таким образом, чтобы вы могли реализовать новую «стратегию» в любое время. В предоставленной вами реализации такой возможности нет. Вместо этого в сообщении должна быть функция «send», которая вызывает MessageDispatcher-> dispatch () (Dispatcher либо параметр, либо член var), а новые классы HTMLDispatcher и TextDispatcher будут реализовывать «диспетчеризацию» соответствующими способами (это позволяет другим диспетчерам выполнять другая работа)
К сожалению, PHP не подходит для реализации шаблона стратегии. Языки, поддерживающие перегрузку методов, здесь работают лучше - представьте, что у вас есть два метода с одним и тем же именем: отправка (HTMLMessage $ m) и отправка (TextMessage $) - теперь в строго типизированном языке компилятор / интерпретатор будет автоматически использовать правильную «стратегию» на основе тип параметра. Кроме того, я не думаю, что открытость для реализации новой стратегии - это суть паттерна стратегии. Конечно, это хорошо, но часто не является обязательным требованием.
Ваш пример очень хорош, и это хорошее решение. Но, как и все PHP, это всегда обходной путь. Приближаемся к взлому (например, traits). Это никогда не является частью хорошего программирования и добавляет бесполезную сложность для реализации того, что должно быть является частью хорошего языка. Подробнее об этом во втором комментарии.
Предположим, у вас есть класс Tracing (это всего лишь образец), в котором вы хотите иметь общие вещи, такие как отладка в файл, отправка SMS для критических проблем и т. д. Все ваши классы - дети этого класса. Теперь предположим, что вы хотите создать класс Exception, который должен иметь эти функции (= дочерний элемент Tracing). Этот класс должен быть дочерним по отношению к Exception. Как спроектировать такие штуки с множественным наследованием без? Да, у вас всегда может быть решение, но вы всегда будете близки к взлому. А взлом = дорогостоящее решение в долгосрочной перспективе. Конец истории.
Оливье Понс, я не думаю, что создание подклассов Tracing будет правильным решением для вашего варианта использования. Что-то столь же простое, как наличие абстрактного класса трассировки со статическими методами Debug, SendSMS и т. д., Которые затем можно вызывать из любого другого класса с помощью Tracing :: SendSMS () и т. д. Другие ваши классы не являются «типами» трассировки, они «используют» трассировку. Примечание: некоторые люди могут предпочесть синглтон статическим методам; Я предпочитаю использовать статические методы, когда это возможно.
Все равно ответ, но класс диспетчеризации, которому необходимо знать внутреннюю структуру класса сообщений, по-прежнему является запретом. И добавление нового типа сообщения потребует изменения класса отправки. Сильное сцепление.
У меня есть пара вопросов, чтобы прояснить, что вы делаете:
1) Содержит ли ваш объект сообщения только сообщение, например тело, получатель, время расписания? 2) Что вы собираетесь делать со своим объектом приглашения? Нужно ли рассматривать его отдельно по сравнению с EmailMessage? 3) Если да, то ЧТО в нем такого особенного? 4) Если это так, то почему типы сообщений нужно обрабатывать по-другому для приглашения? 5) Что делать, если вы хотите отправить приветственное сообщение или сообщение ОК? Это тоже новые объекты?
Похоже, вы пытаетесь объединить слишком много функций в набор объектов, которые должны быть связаны только с хранением содержимого сообщения, а не с тем, как его следует обрабатывать. Видите ли, для меня нет разницы между приглашением и стандартным сообщением. Если приглашение требует особой обработки, то это означает логику приложения, а не тип сообщения.
Например: система, которую я построил, имела объект общего базового сообщения, который был расширен до SMS, электронной почты и других типов сообщений. Однако: они не были расширены - сообщение-приглашение представляло собой просто заранее определенный текст, который должен был быть отправлен через сообщение типа Электронная почта. Конкретное приложение-приглашение будет касаться проверки и других требований к приглашению. В конце концов, все, что вы хотите сделать, это отправить сообщение X получателю Y, которое должно быть отдельной системой.
Это и вопрос, и решение ....
А как насчет волшебных методов _вызов(),_get (), __set ()? Я еще не тестировал это решение, но что, если вы создадите класс multiInherit. Защищенная переменная в дочернем классе может содержать массив наследуемых классов. Конструктор в мультиинтерфейсном классе может создавать экземпляры каждого из наследуемых классов и связывать их с частным свойством, например _ext. Метод __call () может использовать функцию method_exists () для каждого из классов в массиве _ext, чтобы найти правильный метод для вызова. __get () и __set могут использоваться для поиска внутренних свойств, или, если вы эксперт со ссылками, вы можете сделать свойства дочернего класса и унаследованных классов ссылками на одни и те же данные. Множественное наследование вашего объекта будет прозрачным для кода, использующего эти объекты. Кроме того, внутренние объекты могут напрямую обращаться к унаследованным объектам, если это необходимо, если массив _ext индексируется по имени класса. Я задумал создать этот суперкласс, но еще не реализовал его, так как считаю, что если он сработает, это может привести к развитию некоторых плохих привычек программирования.
Думаю, это возможно. Он объединит функциональность нескольких классов, но не будет наследовать их (в смысле instanceof).
И это наверняка не позволит разрешить переопределения, как только внутренний класс вызовет self :: <whatever>
Если я могу процитировать Фила в эта ветка ...
PHP, like Java, does not support multiple inheritance.
Coming in PHP 5.4 will be traits which attempt to provide a solution to this problem.
In the meantime, you would be best to re-think your class design. You can implement multiple interfaces if you're after an extended API to your classes.
И Крис ....
PHP doesn't really support multiple inheritance, but there are some (somewhat messy) ways to implement it. Check out this URL for some examples:
http://www.jasny.net/articles/how-i-php-multiple-inheritance/
Думаю, у них обоих есть полезные ссылки. Не могу дождаться, чтобы опробовать трейты или, может быть, несколько миксинов ...
Черты характера - это то, что нужно
Как насчет класса приглашения прямо под классом сообщений?
Итак, иерархия идет:
Сообщение
--- Приглашение
------ TextMessage
------ Сообщение электронной почты
А в классе Invitation добавьте функциональность, которая была в InvitationTextMessage и InvitationEmailMessage.
Я знаю, что Приглашение на самом деле не является типом сообщения, это скорее функциональность сообщения. Так что я не уверен, хороший ли это объектно-ориентированный дизайн или нет.
Я использую трейты в PHP 5.4 как способ решения этой проблемы. http://php.net/manual/en/language.oop5.traits.php
Это позволяет использовать классическое наследование с расширениями, но также дает возможность помещать общие функции и свойства в «признак». Как сказано в руководстве:
Traits is a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
Не так много случаев, когда наследование (или даже множественное наследование) является оправданным. Посмотрите на принципы SOLID. Предпочитайте композицию наследованию.