Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Num momento de brisa estou aqui pensando sobre a diferença de uma interface para um "abstract" e gostaria da opinião de vocês.
Eu devo usar uma interface quando eu quero "obrigar" meu objeto a trabalhar com todos aqueles métodos.
Já para usar uma classe abstrata eu devo ter em mente que eu não sou "obrigado" a trabalhar com todos os métodos que aquele tipo me oferece.
Exemplo básico.
Interface carro{
public function andar();
}
class Palio implements carro(){
public function andar(){
echo 'andando;'
}
}abstract class carro{
public function andar();
public function ligarArCondicionado(){
echo 'ar condicionado ligado';
}
}
class Palio extends carro(){
public function andar(){
echo 'andando;'
}
}Vlw, galera.
Ainda que algumas linguagens permitam, a regra de ouro é: Interfaces não trazem implementação.
Interfaces descrevem apenas as assinaturas dos métodos e - em alguns casos - constantes, que podem significar valores padrão para métodos
interface Number {
const ZERO = 0;
public function __construct($value = Number::ZERO);
}
A partir do momento que se torna necessário incluir qualquer implementação, a interface passa a ser uma classe abstrata.
Vou sair um pouco do escopo do tópico, mas o exemplo que você deu sobre o ar condicionado do carro é um bom exemplo de onde NÃO usar herança.
Por quê?
Suponha que você tenha:
abstract class Car {
public function accelerate() {}
public function steer() {}
public function turnOnAirConditioning() {}class SpartanCar {
public function turnOnAirConditioning() { throw new Exception('No air conditioning in this car!'); }
}Na maioria das vezes, algo assim é quase um crime, que vai levar a um erro fatal no seu código quando você menos esperar.
Aí você pensa:
Poxa, vamos tirar o ar condicionado dessa classe base aí...
abstract class Car {
public function accelerate() {}
public function steer() {}
public function break() {}class Ferrari extends Car {
public function turnOnAirConditioning() {}
}
class Lamborghini extends Car {
public function turnOnAirConditioning() {}
}Mas isso é fácil de resolver: é só criar uma outra classe abstrata que estende a primeira e que tenha a implementação do ar-condicionado.
abstract AirConditioningEnabledCar class Car {
public function turnOnAirConditioning() {}
}Agora eu faço:
class Ferrari extends AirConditioningEnabledCar {}
Problema resolvido!
Só que não, jovem Padawan...
Essa solução funciona bem se a única modificação possível for o ar condicionado.
Suponha agora que você também pode adicionar freios ABS no seu carro. Como fazer?
abstract AbsEnabledCar class Car {
public function break() {} // changes implementation
}E se o seu carro tiver ambos freios ABS e ar condicionado? Você vai querer perpetrar o horror que é:
abstract AbsAndAirConditioningEnabledCar class Car {}
Mesmo que você ache que está tudo bem agora, o que acontece se você puder adicionar air bags também e sabendo que qualquer carro pode ter qualquer combinação desses 3 opcionais?
Você está com um problema exponencial em mãos! Você está em meio a um mar de DRY e KISS.
Design Pattern: Decorator
Objetivo
Você precisa modificar o comportamento de objetos individuais em tempo de execução. Usar herança não é plausível porque ela é estática e se aplica à classe como um todo, não a objetos.
Discussão
A solução mais comum para essa classe de problemas envolve encapsular o objeto original em uma interface. São criados também classes modificadoras desse objeto original ("decoradores"), que alteram uma ou mais funcionalidades e também implementam aquela interface.
Para poder modificar o comportamento do objeto original, esses decoradores mantêm uma referência para o objeto original ou para uma composição entre outros decoradores e o objeto original.
Estrutura
Descrever padrões verbalmente é sempre complicado, por isso, costumamos utilizar diagramas de classe para facilitar seu entendimento:
/applications/core/interface/imageproxy/imageproxy.php?img=http://www.codeproject.com/KB/cs/LinqQueryWrappers/decorator_pattern.jpg&key=37568960bb7d851c04713edb3d3aa8cc3ceed76c9b7aa3e2020e84d383a48289" alt="decorator_pattern.jpg" />
Exemplo
Voltando ao seu exemplo, teríamos algo assim:
interface ICar { //NOT from Apple!!!
public function accelerate();
public function break();
public function steer();
public function turnOnVentilation(); // regular ventilation all cars have
public function turnOffVentilation(); // regular ventilation all cars have
}
abstract class AbstractCar implements ICar {
public function accelerate() {}
public function break(){ }
public function steer(){ }
public function turnOnVentilation(){ }
public function turnOffVentilation(){ }
}
class Mustang extends AbstractCar { }
Até aí nada de muito diferente. Vamos agora aos nossos decoradores:
abstract class AbstractCarDecorator implements ICar {
private $origCar;
public function __construct(ICar $origCar) {
$this->origCar = $origCar;
}
public function accelerate() {
$this->origCar->accelerate(); // by default, delegates to the original object
}
// All methods bellow do the same: delegate the execution of the method with no changes
public function break(){ }
public function steer(){ }
public function turnOnVentilation(){ }
public function turnOffVentilation(){ }
}
class AirConditioningDecorator extends AbstractCarDecorator {
// The following two methods are not part of the interface
public function turnOnAirConditioning() {}
public function turnOffAirConditioning() {}
public function isOnAirConditioning() {}
/* Now we change the implementation of these two methods
* to make them use the air conditioning capability
*/
public function turnOnVentilation() { }
public function turnOffVentilation() { }
}
class AbsDecorator extends AbstractCarDecorator {
// changes the breaking process
public function break() { }
}$spartanCar = new Mustang(); // sem ar e sem ABS
$spartanCar->accelerate(); // ...
$secureCar = new AbsDecorator(new Mustang());
$secureCar->accelerate(); // será delegado ao objeto Mustang
$secureCar->break(); // será executado o método de freio do objeto Abs
$luxuryCar = new AirConditioningDecorator(new AbsDecorator(new Mustang());
$luxuryCar->turnOnAirConditioning();
$luxuryCar->turnOnVentilation(); // liga a ventilação com ar condicionado
Note que para os métodos não pertencentes à interface, só é possível fazer o acesso se eles forem parte do decorador mais externo. Se fizéssemos:
$luxuryCar = new Abs(new AirConditioningDecorator(new Mustang());
$luxuryCar->turnOnAirConditioning(); // fatal error
Obteríamos um erro fatal, já que [inline]AbsDecorator[/inline] não possui um método [inline]turnOnAirConditioning[/inline].
Existem algumas formas de tornar essa chamada possível, mas para não estender demais o post, não vou colocá-las aqui.
Ah, e se agora eu quiser um carro com air bag, como faço?
Assim:
class AirBagDecorator extends AbstractCarDecorator {
// changes the breaking process, if too abrupt, air bags will inflate
public function break() { }
}
$ultraLuxuryCar = new AirBagDecorator(new AbsDecorator(new AirConditioningDecorator(new Mustang()));Excelente explicação Henrique, aliás acho que tocou no ponto certo do tópico.
//NOT from Apple!!!
rsrs
No final não ficou estranho esse encapsulamento?
$ultraLuxuryCar = new AirBag(new Abs(new AirConditioning(new Mustang()));
Air Bag chama o ASB que chama o arcondicionado que cria o carro.
pareceu estranho por que esses itens não tem relação.
Digo isso pois estou tentando trazer isso para o mundo real.
>
No final não ficou estranho esse encapsulamento?
$ultraLuxuryCar = new AirBag(new Abs(new AirConditioning(new Mustang()));
Air Bag chama o ASB que chama o arcondicionado que cria o carro.
pareceu estranho por que esses itens não tem relação.
Digo isso pois estou tentando trazer isso para o mundo real.
Não, meu amigo. Regra dos parênteses. É de dentro pra fora!
AirBag chamaria ABS se fosse algo assim
function airBag() {
abs();
}
Pela matemática básica, primeiro temos o carro (mustang), então adicionamos ar condenado, abs e, por fim, os albergues.
A leitura ficou um pouco estranha porque o @Henrique Barcelos, apesar da qualidade do post, não se esforçou o suficiente para nomear as coisas. Sabemos que ele é capaz de mais que isso :D
>
$luxuryCar = new Abs(new AirConditioning(new Mustang());
$luxuryCar->turnOnAirConditioning(); // fatal error
Obteríamos um erro fatal, já que [inline]Abs[/inline] não possui um método [inline]turnOnAirConditioning[/inline].
Existem algumas formas de tornar essa chamada possível, mas para não estender demais o post, não vou colocá-las aqui.
Só por curiosidade Henrique, poderia explicar essa forma de chamada direto do objeto [inline]Abs[/inline]?
Ainda estou um pouco confuso quanto essas chamadas já que mesmo com uma inversão fica confuso pois o ABS parece estar "dentro" do ar e isso tudo dentro do airBag...
A leitura ficou um pouco estranha porque o Henrique Barcelos, apesar da qualidade do post, não se esforçou o suficiente para nomear as coisas. Sabemos que ele é capaz de mais que isso
É que estou no trabalho... eahiuehaueaheauhe... Aproveitei uma janelinha aqui e dei uma passada no fórum...
E na verdade, no mundo da TI, só existem 2 coisas difíceis: invalidar cache e dar nome aos bois...
Só por curiosidade Henrique, poderia explicar essa forma de chamada direto do objeto Abs?
A forma mais completa é usando reflection e [inline]__call()[/inline]. Mais tarde eu tento postar um exemplo.
Não sou especialista em POO (ainda :yes: ). Recentemente fiz um curso de PHP OO e aprendi o seguinte:
abstract class Conta{
class ContaPoupanca extends Conta{
Espero ter contribuído.