Множественное наследование в PHP


97

Я ищу хороший чистый способ обойти тот факт, что PHP5 по-прежнему не поддерживает множественное наследование. Вот иерархия классов:

Сообщение
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

У двух типов классов Invitation * много общего; Я бы хотел иметь общий родительский класс, Приглашение, от которого они оба унаследовали бы. К сожалению, у них также много общего со своими нынешними предками ... TextMessage и EmailMessage. Здесь классическая тяга к множественному наследованию.

Какой самый легкий подход к решению проблемы?

Спасибо!


4
Существует не так много случаев, когда наследование (или даже множественное наследование) оправдано. Посмотрите на принципы SOLID. Предпочитайте композицию наследованию.
Ондржей Миртес

2
@ OndřejMirtes, что вы имеете в виду - "не так много случаев, когда наследование оправдано?"?
styler1972

12
То есть - наследование приносит больше проблем, чем пользы (посмотрите на принцип подстановки Лискова). С композицией можно решить практически все и избавиться от головной боли. Наследование также является статическим - это означает, что вы не можете изменить то, что уже написано в коде. Но композицию можно использовать во время выполнения, и вы можете динамически выбирать реализации - например, повторно использовать один и тот же класс с разными механизмами кэширования.
Ондржей Миртес

5
PHP 5.4 имеет "черты": stackoverflow.com/a/13966131/492130
f.ardelian

1
Я бы посоветовал новичкам никогда не использовать наследование . В общем, только две ситуации, в которых разрешено наследование: 1) при создании библиотеки, поэтому пользователи пишут меньше кода, и 2) когда руководитель проекта требует, чтобы вы использовали его
gurghet

Ответы:


141

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

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <jdoe@yahoo.com>';
$m->to = 'Random Hacker <rh@gmail.com>';
$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, а алгоритм отправки делегируется диспетчеру. Это называется паттерном стратегии, вы можете прочитать о нем подробнее здесь .


13
Невероятно обширный ответ, спасибо! Я кое-что узнал сегодня!
Алекс Вайнштейн,

26
... Я знаю, что это немного устарело (я искал, есть ли у PHP MI ... просто из любопытства). Я не думаю, что это хороший пример шаблона стратегии. Шаблон стратегии разработан таким образом, чтобы вы могли реализовать новую «стратегию» в любое время. В предоставленной вами реализации такой возможности нет. Вместо этого в сообщении должна быть функция «send», которая вызывает MessageDispatcher-> dispatch () (Dispatcher либо параметр, либо переменная-член), а новые классы HTMLDispatcher и TextDispatcher будут реализовывать «диспетчеризацию» соответствующими способами (это позволяет другим диспетчерам выполнять другие работы)
Теренс Хонлес

12
К сожалению, PHP не подходит для реализации шаблона стратегии. Языки, поддерживающие перегрузку методов, здесь работают лучше - представьте, что у вас есть два метода с одним и тем же именем: отправка (HTMLMessage $ m) и отправка (TextMessage $) - теперь в строго типизированном языке компилятор / интерпретатор будет автоматически использовать правильную «стратегию» на основе тип параметра. Кроме того, я не думаю, что открытость для реализации новой стратегии - это суть паттерна стратегии. Конечно, это хорошо, но часто это не обязательно.
Michał Rudnicki,

2
Предположим, у вас есть класс Tracing(это всего лишь образец), в котором вы хотите иметь общие вещи, такие как отладка в файл, отправка SMS для критических проблем и так далее. Все ваши классы - дети этого класса. Теперь предположим, что вы хотите создать класс, Exceptionкоторый должен иметь эти функции (= дочерний элемент Tracing). Этот класс должен быть дочерним по отношению к классу Exception. Как создать такой материал без множественного наследования? Да, у вас всегда может быть решение, но вы всегда будете близки к взлому. А взлом = дорогое решение в долгосрочной перспективе. Конец истории.
Olivier Pons

1
Оливье Понс, я не думаю, что создание подкласса Tracing будет правильным решением для вашего варианта использования. Что-то столь же простое, как наличие абстрактного класса трассировки со статическими методами Debug, SendSMS и т. Д., Который затем можно вызвать из любого другого класса с помощью Tracing :: SendSMS () и т. Д. Другие ваши классы не являются «типами» трассировки, они «используют» трассировку. Примечание: некоторые люди могут предпочесть синглтон статическим методам; Я предпочитаю использовать статические методы, а не синглтоны, где это возможно.

15

Может быть, вы можете заменить отношение «есть-а» на отношение «имеет-а»? Приглашение может содержать сообщение, но не обязательно, чтобы оно было «есть». Приглашение fe может быть подтверждено, что плохо сочетается с моделью сообщения.

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


9

Если я могу процитировать Фила в этой ветке ...

PHP, как и Java, не поддерживает множественное наследование.

В PHP 5.4 появятся особенности, которые попытаются предоставить решение этой проблемы.

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

И Крис ....

PHP на самом деле не поддерживает множественное наследование, но есть несколько (несколько запутанных) способов его реализовать. Посмотрите этот URL-адрес, чтобы увидеть несколько примеров:

http://www.jasny.net/articles/how-i-php-multiple-inheritance/

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


1
Черты характера - это лучший способ
Джонатан

6

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

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


4

Я использую трейты в PHP 5.4 как способ решения этой проблемы. http://php.net/manual/en/language.oop5.traits.php

Это позволяет использовать классическое наследование с расширениями, но также дает возможность помещать общие функции и свойства в «признак». Как сказано в руководстве:

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



3

Это и вопрос, и решение ....

А как насчет волшебного _ call (),_get (), __set () методы? Я еще не тестировал это решение, но что, если вы создадите класс multiInherit. Защищенная переменная в дочернем классе может содержать массив наследуемых классов. Конструктор в мультиинтерфейсном классе может создавать экземпляры каждого из наследуемых классов и связывать их с частным свойством, например _ext. Метод __call () может использовать функцию method_exists () для каждого из классов в массиве _ext, чтобы найти правильный метод для вызова. __get () и __set могут использоваться для поиска внутренних свойств, или, если вы эксперт со ссылками, вы можете сделать свойства дочернего класса и унаследованных классов ссылками на одни и те же данные. Множественное наследование вашего объекта будет прозрачным для кода, использующего эти объекты. Также, внутренние объекты могут напрямую обращаться к унаследованным объектам, если это необходимо, если массив _ext индексируется по имени класса. Я задумал создать этот суперкласс, но еще не реализовал его, так как считаю, что если он сработает, это может привести к развитию некоторых плохих привычек программирования.


Я думаю, это возможно. Он объединит функциональность нескольких классов, но не будет наследовать их (в смысле instanceof)
user102008

И это наверняка не позволит разрешить переопределения, как только внутренний класс вызовет self :: <whatever>
Фил Лелло

1

У меня есть пара вопросов, чтобы прояснить, что вы делаете:

1) Содержит ли ваш объект сообщения просто сообщение, например, тело, получателя, расписание? 2) Что вы собираетесь делать со своим объектом приглашения? Нужно ли рассматривать его отдельно по сравнению с EmailMessage? 3) Если да, то ЧТО в этом особенного? 4) Если это так, то почему типы сообщений для приглашения нужно обрабатывать по-другому? 5) Что делать, если вы хотите отправить приветственное сообщение или сообщение ОК? Это тоже новые объекты?

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

Например: система, которую я построил, имела объект общего базового сообщения, который был расширен до SMS, электронной почты и других типов сообщений. Однако: они не были расширены - сообщение-приглашение представляло собой просто заранее определенный текст, который должен был быть отправлен через сообщение типа Электронная почта. Конкретное приложение для приглашения будет касаться проверки и других требований для приглашения. В конце концов, все, что вы хотите сделать, это отправить сообщение X получателю Y, которое должно быть отдельной системой.


0

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


0

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


5
Интерфейсы не позволяют реализовать конкретные функции, поэтому здесь они бесполезны.
Алекс Вайнштейн,

1
В отличие от классов интерфейсы поддерживают множественное наследование.
Крейг Льюис,

-1

Как насчет класса приглашения прямо под классом сообщений?

Итак, иерархия идет:

Сообщение
--- Приглашение
------ TextMessage
------ EmailMessage

А в классе Invitation добавьте функциональность, которая была в InvitationTextMessage и InvitationEmailMessage.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.