Багаторазове спадкування в PHP


97

Я шукаю хороший чистий спосіб подолати той факт, що PHP5 все ще не підтримує багаторазове успадкування. Ось ієрархія класів:

Повідомлення
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- ЗапрошенняEmailMessage

Два класи запрошення * мають багато спільного; Я хотів би мати спільний батьківський клас, запрошення, від якого вони будуть успадковані. На жаль, вони також мають багато спільного з їх нинішніми предками ... TextMessage та EmailMessage. Класичне прагнення до багаторазового спадкування тут.

Який найбільш легкий підхід до вирішення проблеми?

Дякую!


4
Не так багато випадків, коли спадкування (або навіть багаторазове успадкування) є виправданим. Подивіться на принципи SOLID. Віддавайте перевагу складу над спадщиною.
Ondřej Mirtes

2
@ OndřejMirtes, що ви маєте на увазі - "не так багато випадків, коли спадкування є виправданим."?
styler1972

12
Я маю на увазі - успадкування приносить більше проблем, ніж користі (дивіться на принцип заміни Ліскова). Ви можете вирішити майже все за допомогою складу і врятувати багато головних болів. Спадкування також є статичним - це означає, що ви не можете змінити те, що вже написано в коді. Але композицію можна використовувати під час виконання, і ви можете динамічно вибирати реалізацію - наприклад, повторно використовувати той же клас з різними механізмами кешування.
Ondřej Mirtes

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);

Таким чином ви можете додати певну спеціалізацію до класу повідомлень:

$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 ... просто для цікавості). Я не думаю, що це хороший приклад стратегії. Шаблон стратегії розроблений так, що ви можете в будь-який час реалізувати нову "стратегію". Надана вами реалізація не має такої здатності. Натомість повідомлення повинно мати функцію "відправити", яка викликає MessageDispatcher-> dispatch () (Диспетчер або парам, або вар-член), а нові класи HTMLDispatcher & TextDispatcher будуть реалізовувати "диспетчеризацію" відповідними способами (це дозволяє іншим диспетчерам робити інша робота)
Теренс Гонлес

12
На жаль, PHP не чудово підходить для реалізації стратегії. Мови, які підтримують перевантажувальний метод, працюють тут краще - уявіть, що у вас є два однойменних методу: диспетчерська (HTMLMessage $ m) та диспетчерська (TextMessage $) - тепер у сильно набраному мові компілятор / перекладач автоматично використовує правильну "стратегію" на основі тип параметра. Окрім цього, я не думаю, що відкритість для впровадження нової стратегії є суттю Стратегічної моделі. Звичайно, це приємно мати, але це часто не є вимогою.
Міхал Рудницький

2
Припустимо, у вас є клас Tracing(це лише зразок), де ви хочете мати загальні речі, такі як налагодження у файлі, надсилати SMS для критичної проблеми тощо. Усі ваші заняття - діти цього класу. Тепер припустимо, ви хочете створити клас, Exceptionякий повинен мати ці функції (= дочірня частина Tracing). Цей клас повинен бути дитиною Exception. Як ви конструюєте такі речі без багаторазового успадкування? Так, у вас завжди може бути рішення, але ви завжди наблизитесь до злому. І хакерство = дороге рішення в довгостроковій перспективі. Кінець історії.
Олів'є Понс

1
Олів'є Понс, я не думаю, що відстеження підкласифікації було б правильним рішенням для Вашого випадку використання. Щось таке просте, як наявність абстрактного класу відстеження статичними методами Debug, SendSMS тощо, який потім можна викликати з будь-якого іншого класу з Tracing :: SendSMS () тощо. Ваші інші класи не є "типами" Tracing, вони 'використовують' Tracing. Примітка: деякі люди можуть віддавати перевагу синглу перед статичними методами; Я вважаю за краще використовувати статичні методи над одиночними, де це можливо.

15

Можливо, ви можете замінити відношення "є-а" на відношення "має-а"? У запрошенні може бути Повідомлення, але воно не обов'язково повинне бути "є-а". Можливо, буде підтверджено запрошення fe, яке не дуже добре поєднується з моделлю повідомлення.

Шукайте "склад проти успадкування", якщо вам потрібно знати більше про це.


9

Якщо я можу процитувати Філа в цій темі ...

PHP, як і Java, не підтримує багаторазове успадкування.

Прийде в PHP 5.4 буде ознаками, які намагаються вирішити цю проблему.

Тим часом вам було б краще заново продумати дизайн класу. Ви можете реалізувати кілька інтерфейсів, якщо ви користуєтесь розширеним API для своїх класів.

А Кріс….

PHP насправді не підтримує багатократне успадкування, але є кілька (дещо брудно) способів його реалізації. Ознайомтеся з цією URL-адресою для деяких прикладів:

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

Думали, що вони обоє мають корисні зв’язки. Не можна чекати, щоб спробувати риси чи, можливо, деякі комбінації ...


1
Риси - це шлях
Джонатан

6

Рамка Symfony має для цього плагін mixin , можливо, ви захочете перевірити це - навіть лише ідеї, якщо не використовувати їх.

Відповідь "дизайн шаблону" - це абстрагування спільної функціональності в окремий компонент і складання під час виконання. Подумайте, як вилучити функцію запрошення як клас, який асоціюється з вашими класами повідомлень іншим способом, ніж успадкування.


4

Я використовую риси в PHP 5.4 як спосіб вирішення цього питання. http://php.net/manual/en/language.oop5.traits.php

Це дозволяє класичне успадкування з розширенням, але також дає можливість розміщення загальних функціональних можливостей та властивостей у "ознаці". Як зазначено в посібнику:

Риси ознак - це механізм повторного використання коду в єдиних мовах успадкування, таких як PHP. Ознака має на меті зменшити деякі обмеження одинакового успадкування, дозволяючи розробнику вільно використовувати набори методів у кількох незалежних класах, що живуть у різних ієрархіях класів.



3

Це і питання, і рішення ....

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


Я думаю, що це можливо. Він буде поєднувати функціональність декількох класів, але насправді не успадковуватиме їх (у розумінні instanceof)
user102008

І це, безумовно, не зможе дозволити переосмислення, як тільки внутрішній клас зробить заклик до себе :: <що завгодно>
Phil Lello

1

У мене є кілька запитань, щоб уточнити, що ви робите:

1) Чи є ваше повідомлення об'єктом просто містить повідомлення, наприклад, тіло, одержувач, час розкладу? 2) Що ви маєте намір зробити з вашим об’єктом запрошення? Чи потрібно до цього звертатися спеціально порівняно з електронною поштою? 3) Якщо так, ЩО таке особливе в цьому? 4) Якщо це так, то чому для типів повідомлень для запрошення потрібно по-різному поводитися? 5) Що робити, якщо ви хочете надіслати привітання або ОК? Вони теж нові об’єкти?

Це здається, що ви намагаєтеся об'єднати занадто багато функціональності в набір об'єктів, які повинні стосуватися лише вмісту повідомлення - а не того, як з ним слід обробляти. Мені, бачите, немає різниці між запрошенням або звичайним повідомленням. Якщо запрошення вимагає особливої ​​обробки, це означає логіку програми, а не тип повідомлення.

Наприклад: система, яку я створив, мала об'єкт спільного базового повідомлення, який було розширено на SMS, електронну пошту та інші типи повідомлень. Однак: вони не були розширені далі - запрошення було просто заздалегідь визначеним текстом для надсилання через повідомлення типу електронної пошти. Конкретна заявка на запрошення стосуватиметься підтвердження та інших вимог щодо запрошення. Зрештою, все, що ви хочете зробити, - це надіслати повідомлення X одержувачу Y, яке повинно бути дискретною системою.


0

Така ж проблема, як і Java. Спробуйте використовувати інтерфейси з абстрактними функціями для вирішення цієї проблеми


0

PHP підтримує інтерфейси підтримки. Це може бути хорошою ставкою, залежно від ваших випадків використання.


5
Інтерфейси не дозволяють реалізувати конкретні функції, тому вони тут не корисні.
Алекс Вайнштейн

1
Інтерфейси підтримують множинне спадкування, на відміну від класів.
Крейг Льюїс

-1

Як щодо класу запрошення прямо під класом повідомлення?

тому ієрархія іде:

Повідомлення
--- Запрошення
------ Текстове повідомлення ------ Електронна
пошта

А в клас запрошення додайте функціонал, який був у InvitationTextMessage та InvitationEmailMessage.

Я знаю, що запрошення насправді не є типом повідомлення, це більше функціональність повідомлення. Тож я не впевнений, це хороший дизайн OO чи ні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.