Herança múltipla em PHP

97

Estou procurando uma maneira boa e limpa de contornar o fato de que o PHP5 ainda não oferece suporte a herança múltipla. Esta é a hierarquia de classes:

Message
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

Os dois tipos de classes Convite * têm muito em comum; Eu adoraria ter uma classe pai comum, Convite, da qual ambos herdariam. Infelizmente, eles também têm muito em comum com seus ancestrais atuais ... TextMessage e EmailMessage. Desejo clássico de herança múltipla aqui.

Qual é a abordagem mais leve para resolver o problema?

Obrigado!

Alex Weinstein
fonte
4
Não há muitos casos em que a herança (ou mesmo a herança múltipla) seja justificável. Veja os princípios SOLID. Prefira a composição à herança.
Ondřej Mirtes
2
@ OndřejMirtes o que você quer dizer - "não há muitos casos em que a herança é justificável."?
styler 1972,
12
Quer dizer - a herança traz mais problemas do que benefícios (veja o princípio de substituição de Liskov). Você pode resolver quase tudo com composição e evitar muitas dores de cabeça. A herança também é estática - isso significa que você não pode alterar o que já está escrito no código. Mas a composição pode ser usada em tempo de execução e você pode escolher implementações dinamicamente - por exemplo, reutilizar a mesma classe com diferentes mecanismos de cache.
Ondřej Mirtes,
5
PHP 5.4 tem "traits": stackoverflow.com/a/13966131/492130
f.ardelian
1
Eu sugeriria que os iniciantes nunca usassem herança . Em geral, as únicas duas situações em que a herança é permitida são: 1) ao construir uma biblioteca, para que os usuários escrevam menos código e 2) quando o líder do projeto exige que você o use
gurghet

Respostas:

141

Alex, na maioria das vezes você precisa de herança múltipla é um sinal de que sua estrutura de objeto está um tanto incorreta. Na situação que você descreveu, vejo que você tem responsabilidade de classe muito ampla. Se Message fizer parte do modelo de negócios do aplicativo, ela não deve se preocupar com a renderização da saída. Em vez disso, você pode dividir a responsabilidade e usar o MessageDispatcher, que envia a mensagem passada usando texto ou backend html. Não sei seu código, mas deixe-me simular desta forma:

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

Desta forma, você pode adicionar alguma especialização à classe 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);

Observe que MessageDispatcher tomaria a decisão de enviar como HTML ou texto sem formatação, dependendo da typepropriedade no objeto Message transmitido.

// 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");
    }
}

Para resumir, a responsabilidade é dividida entre duas classes. A configuração da mensagem é feita na classe InvitationHTMLMessage / InvitationTextMessage e o algoritmo de envio é delegado ao despachante. Isso é chamado de padrão de estratégia, você pode ler mais sobre ele aqui .

Michał Rudnicki
fonte
13
Resposta incrivelmente extensa, obrigado! Aprendi algo hoje!
Alex Weinstein,
26
... Eu sei que isso é um pouco antigo (eu estava pesquisando para ver se o PHP tinha MI ... só por curiosidade) não acho que este seja um bom exemplo de Strategy Pattern. O padrão de estratégia é projetado para que você possa implementar uma nova "estratégia" a qualquer momento. A implementação que você forneceu não apresenta essa capacidade. Em vez disso, Message deve ter a função "enviar" que chama MessageDispatcher-> dispatch () (Dispatcher seja um parâmetro ou membro var), e as novas classes HTMLDispatcher e TextDispatcher implementarão "despacho" em suas respectivas formas (isso permite que outros despachantes façam outro trabalho)
Terence Honles,
12
Infelizmente, o PHP não é ótimo para implementar o padrão Strategy. As linguagens que suportam a sobrecarga de métodos funcionam melhor aqui - imagine que você tem dois métodos com o mesmo nome: dispatch (HTMLMessage $ m) e dispatch (TextMessage $) - agora em linguagem fortemente tipada, o compilador / interpretador empregaria automaticamente a "estratégia" certa baseada em tipo de parâmetro. Além disso, não acho que estar aberto para a implementação de novas estratégias seja a essência do Strategy Pattern. Claro que é bom ter, mas geralmente não é um requisito.
Michał Rudnicki,
2
Suponha que você tenha uma classe Tracing(este é apenas um exemplo) onde deseja ter coisas genéricas como depurar em um arquivo, enviar SMS para problemas críticos e assim por diante. Todas as suas turmas são crianças desta classe. Agora, suponha que você queira criar uma classe Exceptionque deva ter essas funções (= filho de Tracing). Esta classe deve ser filha de Exception. Como você projeta essas coisas sem herança múltipla? Sim, você sempre pode ter uma solução, mas sempre chegará perto de hackear. E hackear = solução cara a longo prazo. Fim da história.
Olivier Pons
1
Olivier Pons, não acho que a subclasse de Rastreamento seria a solução correta para o seu caso de uso. Algo tão simples como ter uma classe abstrata Tracing com métodos estáticos Debug, SendSMS, etc., que pode então ser chamada de qualquer outra classe com Tracing :: SendSMS () etc. Suas outras classes não são 'tipos de' de Tracing, eles 'usam' o rastreamento. Nota: algumas pessoas podem preferir um singleton em vez de métodos estáticos; Eu prefiro usar métodos estáticos em vez de singletons onde possível.
15

Talvez você possa substituir uma relação 'é-um' por uma relação 'tem-um'? Um convite pode ter uma mensagem, mas não precisa necessariamente 'é uma' mensagem. Um convite fe pode ser confirmado, o que não combina com o modelo de mensagem.

Pesquise por 'composição vs. herança' se precisar saber mais sobre isso.

Matthias Kestenholz
fonte
9

Se eu puder citar Phil neste tópico ...

PHP, como Java, não oferece suporte a herança múltipla.

Virão no PHP 5.4 algumas características que tentarão fornecer uma solução para este problema.

Nesse ínterim, seria melhor repensar o design de sua classe. Você pode implementar várias interfaces se desejar uma API estendida para suas classes.

E Chris ....

O PHP realmente não suporta herança múltipla, mas existem algumas maneiras (um tanto confusas) de implementá-lo. Confira este URL para alguns exemplos:

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

Achei que ambos tinham links úteis. Mal posso esperar para experimentar características ou talvez alguns mixins ...

Simon East
fonte
1
As características são o caminho a percorrer
Jonathan
6

O framework Symfony tem um plugin mixin para isso , você pode querer dar uma olhada - mesmo apenas para ter ideias, se não para usá-lo.

A resposta do "padrão de design" é abstrair a funcionalidade compartilhada em um componente separado e compor no tempo de execução. Pense em uma maneira de abstrair a funcionalidade de convite como uma classe que é associada às suas classes de mensagem de alguma forma diferente de herança.

joelhardi
fonte
4

Estou usando traits no PHP 5.4 como forma de resolver isso. http://php.net/manual/en/language.oop5.traits.php

Isso permite a herança clássica com extensões, mas também dá a possibilidade de colocar funcionalidades e propriedades comuns em um 'traço'. Como diz o manual:

Traits é um mecanismo para reutilização de código em linguagens de herança única, como PHP. Um Trait tem como objetivo reduzir algumas limitações de herança única, permitindo que um desenvolvedor reutilize conjuntos de métodos livremente em várias classes independentes que vivem em diferentes hierarquias de classes.

MatthewPearson
fonte
3

Parece que o padrão do decorador pode ser adequado, mas é difícil dizer sem mais detalhes.

danio
fonte
Melhor resposta IMO.
100k,
3

Esta é uma pergunta e uma solução ....

E sobre o mágico _ call (),Métodos _get (), __set ()? Ainda não testei esta solução, mas e se você fizer uma classe multiInherit. Uma variável protegida em uma classe filha pode conter uma matriz de classes para herdar. O construtor na classe de interface múltipla pode criar instâncias de cada uma das classes que estão sendo herdadas e vinculá-las a uma propriedade privada, digamos _ext. O método __call () poderia usar a função method_exists () em cada uma das classes na matriz _ext para localizar o método correto a ser chamado. __get () e __set podem ser usados ​​para localizar propriedades internas, ou se você for um especialista com referências, você pode fazer com que as propriedades da classe filha e as classes herdadas sejam referências aos mesmos dados. A herança múltipla de seu objeto seria transparente para o código que usa esses objetos. Além disso, os objetos internos podem acessar os objetos herdados diretamente se necessário, desde que o array _ext seja indexado pelo nome da classe. Eu imaginei a criação desta superclasse e ainda não a implementei, pois sinto que se funcionar, pode levar ao desenvolvimento de alguns hábitos ruins de programação.

Ralph Ritoch
fonte
Eu acho que isso é viável. Ele combinará a funcionalidade de várias classes, mas na verdade não as herdará (no sentido de instanceof)
user102008
E isso certamente deixará de permitir substituições assim que a classe interna fizer uma chamada para self :: <qualquer>
Phil Lello
1

Tenho algumas perguntas a fazer para esclarecer o que você está fazendo:

1) Seu objeto de mensagem contém apenas uma mensagem, por exemplo, corpo, destinatário, horário de agendamento? 2) O que você pretende fazer com o seu objeto Convite? Ele precisa ser tratado de maneira especial em comparação com uma EmailMessage? 3) Se sim, O QUE há de tão especial nisso? 4) Se for esse o caso, por que os tipos de mensagem precisam ser tratados de maneira diferente para um convite? 5) E se você quiser enviar uma mensagem de boas-vindas ou uma mensagem de OK? Eles também são objetos novos?

Parece que você está tentando combinar muitas funcionalidades em um conjunto de objetos que devem se preocupar apenas em manter o conteúdo de uma mensagem - e não em como deve ser tratado. Para mim, você vê, não há diferença entre um convite ou uma mensagem padrão. Se o convite requer tratamento especial, isso significa lógica do aplicativo e não um tipo de mensagem.

Por exemplo: um sistema que construí tinha um objeto de mensagem base compartilhada que foi estendido para SMS, e-mail e outros tipos de mensagem. No entanto: não foram estendidas mais - uma mensagem de convite era simplesmente um texto predefinido a ser enviado por meio de uma mensagem do tipo E-mail. Um aplicativo de convite específico estaria preocupado com a validação e outros requisitos para um convite. Afinal, tudo o que você deseja fazer é enviar a mensagem X ao destinatário Y, que deve ser um sistema separado por si só.


fonte
0

O mesmo problema do Java. Tente usar interfaces com funções abstratas para resolver esse problema

DeeCee
fonte
0

PHP oferece suporte a interfaces. Essa pode ser uma boa aposta, dependendo de seus casos de uso.

Cheekysoft
fonte
5
As interfaces não permitem implementações de funções concretas, portanto, não são úteis aqui.
Alex Weinstein,
1
As interfaces oferecem suporte à herança múltipla, ao contrário das classes.
Craig Lewis
-1

Que tal uma classe Convite logo abaixo da classe Mensagem?

então a hierarquia vai:

Mensagem
--- Convite
------ TextMessage
------ EmailMessage

E na classe Convite, adicione a funcionalidade que estava em InvitationTextMessage e InvitationEmailMessage.

Eu sei que Convite não é realmente um tipo de Mensagem, é mais uma funcionalidade de Mensagem. Portanto, não tenho certeza se este é um bom design OO ou não.

nube
fonte