Posso estendere una classe usando più di una classe in PHP?


150

Se ho diverse classi con funzioni di cui ho bisogno ma che voglio memorizzare separatamente per l'organizzazione, posso estendere una classe per avere entrambe?

vale a dire class a extends b extends c

modifica: so come estendere le classi una alla volta, ma sto cercando un metodo per estendere istantaneamente una classe usando più classi base - AFAIK non puoi farlo in PHP ma ci dovrebbero essere modi per aggirare senza ricorrere a class c extends b,class b extends a


Usa aggregazione o interfacce. L'ereditarietà multipla non esiste in PHP.
Franck,

2
Sto esaminando le interfacce poiché non sono un grande fan delle gerarchie di grandi classi. Ma non riesco a vedere come le interfacce facciano davvero qualcosa?
Atomicharri,

Le interfacce consentono di "ereditare" solo l'API, non i corpi funzione. Forza la classe a a implementare metodi dall'interfaccia bec. Significa che se vuoi ereditare il comportamento devi aggregare oggetti membro delle classi bec nella tua classe a.
Franck,

2
Prendi in considerazione l'utilizzo di Decoratori sourcemaking.com/design_patterns/decorator o Strategies sourcemaking.com/design_patterns/strategy
Gordon

2
Considera i tratti come la risposta corretta.
Daniel,

Risposte:


170

Rispondere alla tua modifica:

Se vuoi davvero fingere l'ereditarietà multipla, puoi usare la funzione magica __call ().

Questo è brutto anche se funziona dal punto di vista dell'utente di classe A:

class B {
    public function method_from_b($s) {
        echo $s;
    }
}

class C {
    public function method_from_c($s) {
        echo $s;
    }
}

class A extends B
{
  private $c;

  public function __construct()
  {
    $this->c = new C;
  }

  // fake "extends C" using magic function
  public function __call($method, $args)
  {
    $this->c->$method($args[0]);
  }
}


$a = new A;
$a->method_from_b("abc");
$a->method_from_c("def");

Stampa "abcdef"


1
In realtà mi piace l'idea di estendere la classe Ci sono limiti noti nel farlo in questo modo?
Atomicharri,

3
Per quanto ne so, PHP è un linguaggio molto permissivo per i piccoli hack come questo. :) Come altri hanno sottolineato, non è il modo OOP corretto di farlo.
Franck,

64
Non sarai in grado di utilizzare metodi protetti o privati.
wormhit,

1
@wormhit, tuttavia, non lo consiglierei per l'uso in produzione, si può usare ReflectionClass per accedere a metodi privati ​​e protetti.
Denis V,

2
Questo non funzionerà con Implements, che prevede che il metodo esista effettivamente e il comportamento è praticamente indefinito quando più classi base hanno un metodo con lo stesso nome. Confonderà anche la capacità del tuo editor di darti suggerimenti.
Erik,

138

Non è possibile avere una classe che estende due classi base. Non avresti potuto.

// this is NOT allowed (for all you google speeders)
Matron extends Nurse, HumanEntity

Potresti comunque avere una gerarchia come segue ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

e così via.


Puoi spiegare perché è giusto ereditare prima Nurse in Matron, quindi dichiarare l'eredità di HumanEntity in Nurse?
Qwerty,

7
@Qwerty Perché Matron ha le qualità aggiuntive di un'infermiera, mentre un'infermiera ha tutte le qualità di un essere umano. Pertanto, Matron è un'infermiera umana e finalmente ha le capacità di Matron
J-Dizzle,

58

Potresti usare tratti che, si spera, saranno disponibili da PHP 5.4.

I tratti sono un meccanismo per il riutilizzo del codice in singoli linguaggi di ereditarietà come PHP. Un tratto ha lo scopo di ridurre alcune limitazioni dell'ereditarietà singola consentendo a uno sviluppatore di riutilizzare liberamente gruppi di metodi in diverse classi indipendenti che vivono in gerarchie di classi diverse. La semantica della combinazione di tratti e classi è definita in un modo, che riduce la complessità ed evita i problemi tipici associati all'ereditarietà multipla e ai mixin.

Sono riconosciuti per il loro potenziale nel supportare una migliore composizione e riutilizzo, quindi la loro integrazione in versioni più recenti di lingue come Perl 6, Squeak, Scala, Slate e Fortress. I tratti sono stati anche portati su Java e C #.

Ulteriori informazioni: https://wiki.php.net/rfc/traits


Assicurati di non abusarne. Essenzialmente sono funzioni statiche che si applicano agli oggetti. Potresti creare un casino abusandoli.
Nikola Petkanski,

2
Non posso credere che questa non sia la risposta selezionata.
Daniel,

18

Le classi non sono pensate per essere solo raccolte di metodi. Una classe dovrebbe rappresentare un concetto astratto, con stato (campi) e comportamento (metodi) che cambiano lo stato. Usare l'ereditarietà solo per ottenere alcuni comportamenti desiderati suona come una cattiva progettazione di OO, ed è esattamente il motivo per cui molte lingue non consentono l'ereditarietà multipla: al fine di prevenire "l'ereditarietà degli spaghetti", vale a dire estendere 3 classi perché ognuna ha un metodo necessario e finire con una classe che eredita 100 metodi e 20 campi, ma ne utilizza solo e solo 5.


1
Metterò in dubbio la tua affermazione che la richiesta del PO costituisce "cattiva progettazione OO" ; basta guardare l'emergere di Mixin per supportare la posizione secondo cui l'aggiunta di metodi a una classe da più fonti è una buona idea dal punto di vista architettonico. Ti darò comunque che PHP non fornisce un set ottimale di funzionalità linguistiche per ottenere un "design" ottimale, ma ciò non significa che utilizzare le funzionalità disponibili per approssimarlo sia necessariamente una cattiva idea; guarda la risposta di @ Franck.
MikeSchinkel,

15

Ci sono piani per aggiungere presto dei mix-in, credo.

Ma fino ad allora, vai con la risposta accettata. Puoi astrarlo un po 'per creare una classe "estensibile":

class Extendable{
  private $extender=array();

  public function addExtender(Extender $obj){
    $this->extenders[] = $obj;
    $obj->setExtendee($this);
  }

  public function __call($name, $params){
    foreach($this->extenders as $extender){
       //do reflection to see if extender has this method with this argument count
       if (method_exists($extender, $name)){
          return call_user_func_array(array($extender, $name), $params);
       }
    }
  }
}


$foo = new Extendable();
$foo->addExtender(new OtherClass());
$foo->other_class_method();

Si noti che in questo modello "OtherClass" arriva a "conoscere" circa $ pippo. OtherClass deve avere una funzione pubblica chiamata "setExtendee" per impostare questa relazione. Quindi, se i suoi metodi sono richiamati da $ foo, può accedere a $ foo internamente. Tuttavia, non otterrà l'accesso a metodi / variabili privati ​​/ protetti come farebbe una vera classe estesa.


12

Usa i tratti come classi base. Quindi usali in una classe genitore. Estenderlo.

trait business{
  function sell(){

  }

  function buy(){

  }

  function collectMoney(){
  }

}

trait human{

   function think(){

   }

   function speak(){

   }

}

class BusinessPerson{
  use business;
  use human;
  // If you have more traits bring more
}


class BusinessWoman extends BusinessPerson{

   function getPregnant(){

   }

}


$bw = new BusinessWoman();
$bw ->speak();
$bw->getPregnant();

Vedi ora donna d'affari logicamente ereditata da affari e umani entrambi;


Sono bloccato con php 5.3 non c'è supporto per i tratti: D
Nishchal Gautam

Perché non usare il tratto BusinessWomaninvece di BusinessPerson? Quindi puoi avere l'ereditarietà multipla effettiva.
SOFe

@SOFe, perché BusinessMan può anche estenderlo. Quindi chi mai fa affari può estendere gli affari e ottenere i tratti automaticamente
Alice

Il modo in cui lo fai non fa alcuna differenza da ciò che è possibile in eredità singola in cui BusinessPerson estende una classe astratta chiamata umana. Il mio punto è che il tuo esempio non ha davvero bisogno della multi ereditarietà.
SOF,

Credo fino a quando BusinessPerson non fosse necessario, BusinessWoman avrebbe potuto ereditare direttamente altri tratti. Ma quello che ho provato qui è, mantenendo entrambi i tratti in una classe di mediatore e poi usarlo, dove mai richiesto. Quindi, posso dimenticare di includere entrambi i tratti se ancora necessario altrove (come ho descritto BusinessMan). Si tratta di stili di codice. Se ti piace includere direttamente nella tua classe. puoi andare avanti con quello - dato che hai assolutamente ragione.
Alice

9
<?php
// what if we want to extend more than one class?

abstract class ExtensionBridge
{
    // array containing all the extended classes
    private $_exts = array();
    public $_this;

    function __construct() {$_this = $this;}

    public function addExt($object)
    {
        $this->_exts[]=$object;
    }

    public function __get($varname)
    {
        foreach($this->_exts as $ext)
        {
            if(property_exists($ext,$varname))
            return $ext->$varname;
        }
    }

    public function __call($method,$args)
    {
        foreach($this->_exts as $ext)
        {
            if(method_exists($ext,$method))
            return call_user_method_array($method,$ext,$args);
        }
        throw new Exception("This Method {$method} doesn't exists");
    }


}

class Ext1
{
    private $name="";
    private $id="";
    public function setID($id){$this->id = $id;}
    public function setName($name){$this->name = $name;}
    public function getID(){return $this->id;}
    public function getName(){return $this->name;}
}

class Ext2
{
    private $address="";
    private $country="";
    public function setAddress($address){$this->address = $address;}
    public function setCountry($country){$this->country = $country;}
    public function getAddress(){return $this->address;}
    public function getCountry(){return $this->country;}
}

class Extender extends ExtensionBridge
{
    function __construct()
    {
        parent::addExt(new Ext1());
        parent::addExt(new Ext2());
    }

    public function __toString()
    {
        return $this->getName().', from: '.$this->getCountry();
    }
}

$o = new Extender();
$o->setName("Mahdi");
$o->setCountry("Al-Ahwaz");
echo $o;
?>

4
La tua risposta dovrebbe contenere una spiegazione del tuo codice e una descrizione di come risolve il problema.
AbcAeffchen,

1
È quasi autoesplicativo, ma sarebbe bello avere commenti lungo il codice.
Heroselohim,

ora se Ext1 ed Ext2 hanno entrambi una funzione con lo stesso nome, si chiamerà sempre Ext1, non dipenderà affatto dai parametri.
Alice il

8

La risposta attualmente accettata da @Franck funzionerà, ma in realtà non si tratta di ereditarietà multipla ma di un'istanza figlio di classe definita fuori ambito, inoltre esiste una __call()scorciatoia: prendere in considerazione l'utilizzo di $this->childInstance->method(args)qualsiasi luogo in cui sia necessario il metodo della classe ExternalClass nella classe "estesa".

Risposta esatta

No, non puoi, rispettivamente, non proprio, come dice il manuale della extendsparola chiave :

Una classe estesa dipende sempre da una singola classe base, ovvero l'ereditarietà multipla non è supportata.

Vera risposta

Tuttavia, come suggerito correttamente da @adam, questo NON ti proibisce di utilizzare l'ereditarietà multipla gerarchica.

È possibile estendere una classe, con un'altra e un'altra con un'altra e così via ...

Quindi un esempio piuttosto semplice su questo sarebbe:

class firstInheritance{}
class secondInheritance extends firstInheritance{}
class someFinalClass extends secondInheritance{}
//...and so on...

Nota importante

Come avrai notato, puoi eseguire più (2+) integrità per gerarchia solo se hai il controllo su tutte le classi incluse nel processo ; ciò significa che non puoi applicare questa soluzione, ad esempio con classi integrate o con classi che semplicemente non puoi modificarlo - se vuoi farlo, ti rimane la soluzione @Franck - istanze figlio.

... E infine esempio con qualche output:

class A{
  function a_hi(){
    echo "I am a of A".PHP_EOL."<br>".PHP_EOL;  
  }
}

class B extends A{
  function b_hi(){
    echo "I am b of B".PHP_EOL."<br>".PHP_EOL;  
  }
}

class C extends B{
  function c_hi(){
    echo "I am c of C".PHP_EOL."<br>".PHP_EOL;  
  }
}

$myTestInstance = new C();

$myTestInstance->a_hi();
$myTestInstance->b_hi();
$myTestInstance->c_hi();

Quali uscite

I am a of A 
I am b of B 
I am c of C 

6

Ho letto diversi articoli che scoraggiano l'eredità nei progetti (al contrario delle biblioteche / dei quadri) e che incoraggiano a programmare interfacce agaisnt, no contro le implementazioni.
Sostengono anche OO per composizione: se hai bisogno delle funzioni in classe aeb, fai in modo che abbiano membri / campi di questo tipo:

class C
{
    private $a, $b;

    public function __construct($x, $y)
    {
        $this->a = new A(42, $x);
        $this->b = new B($y);
    }

    protected function DoSomething()
    {
        $this->a->Act();
        $this->b->Do();
    }
}

Questo diventa effettivamente la stessa cosa che mostrano Franck e Sam. Naturalmente se scegli di usare esplicitamente la composizione dovresti usare l' iniezione di dipendenza .
MikeSchinkel,

3

L'ereditarietà multipla sembra funzionare a livello di interfaccia. Ho fatto un test su php 5.6.1.

Ecco un codice funzionante:

<?php


interface Animal
{
    public function sayHello();
}


interface HairyThing
{
    public function plush();
}

interface Dog extends Animal, HairyThing
{
    public function bark();
}


class Puppy implements Dog
{
    public function bark()
    {
        echo "ouaf";
    }

    public function sayHello()
    {
        echo "hello";
    }

    public function plush()
    {
        echo "plush";
    }


}


echo PHP_VERSION; // 5.6.1
$o = new Puppy();
$o->bark();
$o->plush();
$o->sayHello(); // displays: 5.6.16ouafplushhello

Non pensavo fosse possibile, ma mi sono imbattuto nel codice sorgente di SwiftMailer, nella classe Swift_Transport_IoBuffer, che ha la seguente definizione:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Non ci ho ancora giocato, ma ho pensato che potesse essere interessante da condividere.


1
interessante e irrilevante.
Yevgeniy Afanasyev il

1

Ho appena risolto il mio problema di "ereditarietà multipla" con:

class Session {
    public $username;
}

class MyServiceResponsetype {
    protected $only_avaliable_in_response;
}

class SessionResponse extends MyServiceResponsetype {
    /** has shared $only_avaliable_in_response */

    public $session;

    public function __construct(Session $session) {
      $this->session = $session;
    }

}

In questo modo ho il potere di manipolare la sessione all'interno di una SessionResponse che estende MyServiceResponsetype, riuscendo comunque a gestire la Sessione da sola.


1

Se si desidera verificare se una funzione è pubblica, consultare questo argomento: https://stackoverflow.com/a/4160928/2226755

E usa il metodo call_user_func_array (...) per molti argomenti o meno.

Come questo :

class B {
    public function method_from_b($s) {
        echo $s;
    }
}

class C {
    public function method_from_c($l, $l1, $l2) {
        echo $l.$l1.$l2;
    }
}

class A extends B {
    private $c;

    public function __construct() {
        $this->c = new C;
    }

    public function __call($method, $args) {
        if (method_exists($this->c, $method)) {
            $reflection = new ReflectionMethod($this->c, $method);
            if (!$reflection->isPublic()) {
                throw new RuntimeException("Call to not public method ".get_class($this)."::$method()");
            }

            return call_user_func_array(array($this->c, $method), $args);
        } else {
            throw new RuntimeException("Call to undefined method ".get_class($this)."::$method()");
        }
    }
}


$a = new A;
$a->method_from_b("abc");
$a->method_from_c("d", "e", "f");


0

PHP non supporta ancora l'ereditarietà di più classi, tuttavia supporta l'ereditarietà di più interfacce.

Vedi http://www.hudzilla.org/php/6_17_0.php per alcuni esempi.


Ancora? Dubito che lo faranno. La moderna OO scoraggia l'eredità multipla, può essere disordinata.
PhiLho il

0

PHP non consente l'ereditarietà multipla, ma è possibile implementare più interfacce. Se l'implementazione è "pesante", fornire un'implementazione scheletrica per ciascuna interfaccia in una classe separata. Quindi, è possibile delegare tutta la classe di interfaccia a queste implementazioni scheletriche tramite il contenimento degli oggetti .


0

Non sapendo esattamente cosa stai cercando di ottenere, suggerirei di esaminare la possibilità di ridisegnare la tua applicazione per utilizzare la composizione piuttosto che l'ereditarietà in questo caso.


0

Sempre una buona idea è quella di creare una classe genitore, con funzioni ... vale a dire aggiungere questa funzionalità a genitore.

E "sposta" tutte le classi che lo usano gerarchicamente verso il basso. Ho bisogno di: riscrivere le funzioni, che sono specifiche.


0

Uno dei problemi di PHP come linguaggio di programmazione è il fatto che puoi avere un'unica eredità. Ciò significa che una classe può ereditare solo da un'altra classe.

Tuttavia, molte volte sarebbe utile ereditare da più classi. Ad esempio, potrebbe essere desiderabile ereditare metodi da un paio di classi diverse al fine di prevenire la duplicazione del codice.

Questo problema può portare a una classe che ha una lunga storia familiare di eredità che spesso non ha senso.

In PHP 5.4 è stata aggiunta una nuova funzionalità della lingua nota come Traits. Un Trait è un po 'come un Mixin in quanto ti consente di mescolare le classi Trait in una classe esistente. Ciò significa che è possibile ridurre la duplicazione del codice e ottenere i vantaggi evitando i problemi di ereditarietà multipla.

Tratti


-1

Questa non è una vera risposta, abbiamo una classe duplicata

... ma funziona :)

class A {
    //do some things
}

class B {
    //do some things
}

la classe copy_B è la copia di tutta la classe B.

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

adesso

class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{}  //extends A & B

-3

la classe A estende B {}

la classe B estende C {}

Quindi A ha esteso sia B che C


2
@rostamiani perché questo metodo modifica anche la classe B. Ciò che vogliamo la maggior parte del tempo è avere due classi non correlate Be C(probabilmente da librerie diverse) e ottenere simultaneamente ereditate A, in modo tale che Acontenga tutto di entrambi Be C, maB non contenga nulla da Ce viceversa.
SOFe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.