Czy mogę rozszerzyć klasę używając więcej niż 1 klasy w PHP?

150

Jeśli mam kilka klas z funkcjami, których potrzebuję, ale chcę przechowywać je oddzielnie dla organizacji, czy mogę rozszerzyć klasę, aby mieć obie?

to znaczy class a extends b extends c

edycja: Wiem, jak rozszerzać klasy pojedynczo, ale szukam metody, aby natychmiast rozszerzyć klasę za pomocą wielu klas bazowych - AFAIK, nie możesz tego zrobić w PHP, ale powinny być sposoby obejścia tego bez uciekania się do class c extends b,class b extends a

atomicharri
źródło
Użyj agregacji lub interfejsów. Dziedziczenie wielokrotne nie istnieje w PHP.
Franck,
2
Patrzę na interfejsy, ponieważ nie jestem wielkim fanem dużych hierarchii klas. Ale nie widzę, jak interfejsy faktycznie coś robią?
atomicharri
Interfejsy umożliwiają „dziedziczenie” tylko interfejsu API, a nie treści funkcji. Wymusza na klasie a implementację metod z interfejsu bi c. Oznacza to, że jeśli chcesz dziedziczyć zachowanie, musisz zagregować obiekty składowe klas bi c w swojej klasie a.
Franck,
2
Rozważ użycie dekoratorów sourcemaking.com/design_patterns/decorator lub Strategies sourcemaking.com/design_patterns/strategy
Gordon
2
Proszę wziąć pod uwagę cechy jako poprawną odpowiedź.
Daniel

Odpowiedzi:

170

Odpowiadając na zmianę:

Jeśli naprawdę chcesz sfałszować wielokrotne dziedziczenie, możesz użyć magicznej funkcji __call ().

To jest brzydkie, chociaż działa z punktu widzenia użytkownika klasy 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");

Wyświetla „abcdef”

Franck
źródło
1
Właściwie całkiem podoba mi się pomysł rozszerzenia klasy. Czy są jakieś znane ograniczenia robienia tego w ten sposób?
atomicharri
3
Z tego, co wiem, nie ma żadnych ograniczeń, PHP jest językiem bardzo liberalnym dla takich małych hacków. :) Jak zauważyli inni, nie jest to jednak właściwa metoda OOP.
Franck
64
Nie będziesz mógł korzystać z metod chronionych ani prywatnych.
wormhit
1
@wormhit, chociaż nie polecałbym go do użytku w środowisku produkcyjnym, można użyć ReflectionClass, aby uzyskać dostęp do prywatnych i chronionych metod.
Denis V,
2
To nie zadziała w przypadku implementacji, które oczekują, że metoda faktycznie istnieje, a zachowanie jest prawie niezdefiniowane, gdy wiele klas podstawowych ma metodę o tej samej nazwie. Zepsuje również zdolność edytora do udzielania wskazówek.
Erik,
138

Nie możesz mieć klasy, która rozszerza dwie klasy bazowe. Nie mogłeś.

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

Możesz jednak mieć następującą hierarchię ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

i tak dalej.

Adam
źródło
Czy możesz wyjaśnić, dlaczego dobrze jest najpierw odziedziczyć Pielęgniarkę po Matronie, a następnie zadeklarować dziedziczenie Ludzkiej Istoty Pielęgniarce?
Qwerty
7
@Qwerty Ponieważ przełożona ma dodatkowe cechy pielęgniarki, podczas gdy pielęgniarka ma wszystkie cechy ludzkie. Dlatego opiekunka jest ludzką pielęgniarką i wreszcie ma możliwości opiekunki
J-Dizzle
58

Możesz użyć cech, które, miejmy nadzieję, będą dostępne w PHP 5.4.

Cechy to mechanizm ponownego wykorzystania kodu w pojedynczych językach dziedziczenia, takich jak PHP. Cecha ma na celu zmniejszenie niektórych ograniczeń pojedynczego dziedziczenia poprzez umożliwienie programiście swobodnego ponownego wykorzystywania zestawów metod w kilku niezależnych klasach żyjących w różnych hierarchiach klas. Semantyka kombinacji cech i klas jest zdefiniowana w sposób, który zmniejsza złożoność i pozwala uniknąć typowych problemów związanych z dziedziczeniem wielokrotnym i mieszankami.

Są znane ze swojego potencjału w zakresie wspierania lepszej kompozycji i ponownego wykorzystania, stąd ich integracja z nowszymi wersjami języków, takich jak Perl 6, Squeak, Scala, Slate i Fortress. Cechy zostały również przeniesione na Javę i C #.

Więcej informacji: https://wiki.php.net/rfc/traits

Nikola Petkanski
źródło
Uważaj, aby ich nie nadużywać. Zasadniczo są to funkcje statyczne, które można zastosować do obiektów. Możesz stworzyć bałagan, nadużywając ich.
Nikola Petkanski
2
Nie mogę uwierzyć, że to nie jest wybrana odpowiedź.
Daniel
18

Zajęcia nie mają być tylko zbiorem metod. Klasa ma reprezentować abstrakcyjną koncepcję, zawierającą zarówno stan (pola), jak i zachowanie (metody), które zmienia stan. Używanie dziedziczenia tylko po to, by uzyskać pożądane zachowanie, brzmi jak zły projekt obiektu obiektowego i jest dokładnie powodem, dla którego wiele języków nie zezwala na wielokrotne dziedziczenie: aby zapobiec „dziedziczeniu spaghetti”, tj. Rozszerzając 3 klasy, ponieważ każda ma metodę, której potrzebujesz, i kończy się na klasa, która dziedziczy 100 metod i 20 pól, ale zawsze używa tylko 5 z nich.

Michael Borgwardt
źródło
1
Nie zgadzam się z Twoim zapewnieniem, że żądanie OP stanowi „zły projekt OO” ; wystarczy spojrzeć na pojawienie się Mixins, aby wesprzeć stanowisko, że dodanie metod do klasy z wielu źródeł jest dobrym pomysłem z architektonicznego punktu widzenia . Podpowiem jednak, że PHP nie zapewnia optymalnego zestawu funkcji językowych do osiągnięcia optymalnego „projektu”, ale nie oznacza to, że używanie dostępnych funkcji do przybliżenia tego jest koniecznie złym pomysłem; spójrz tylko na odpowiedź @ Francka.
MikeSchinkel
15

Sądzę, że są plany dodania wkrótce miksów.

Ale do tego czasu stosuj zaakceptowaną odpowiedź. Możesz to nieco wyodrębnić, aby utworzyć klasę „rozszerzalną”:

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

Zauważ, że w tym modelu „OtherClass” „wie” o $ foo. OtherClass musi mieć funkcję publiczną o nazwie „setExtendee”, aby ustawić tę relację. Następnie, jeśli jego metody są wywoływane z $ foo, może uzyskać dostęp do $ foo wewnętrznie. Nie uzyska jednak dostępu do żadnych prywatnych / chronionych metod / zmiennych, takich jak prawdziwa klasa rozszerzona.

Sam
źródło
12

Użyj cech jako klas podstawowych. Następnie użyj ich w klasie nadrzędnej. Przedłuż to.

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

Spójrzmy teraz na kobietę biznesu, która logicznie odziedziczyła biznes i ludzi;

Alice
źródło
Utknąłem
Dlaczego nie użyć tej cechy BusinessWomanzamiast BusinessPerson? Wtedy możesz mieć rzeczywiste wielokrotne dziedziczenie.
SOFe
@SOFe, ponieważ BusinessMan może go również rozszerzyć. Więc kto kiedykolwiek prowadzi interesy, może poszerzyć biznes i automatycznie otrzymuje cechy
Alice,
Sposób, w jaki to robisz, nie różni się od tego, co jest możliwe w przypadku pojedynczego dziedziczenia, w którym BusinessPerson rozszerza abstrakcyjną klasę o nazwie human. Chodzi mi o to, że twój przykład tak naprawdę nie potrzebuje dziedziczenia wielokrotnego.
SOFe
Wierzę, że do BusinessPerson było to wymagane, BusinessWoman mogła bezpośrednio odziedziczyć inne cechy. Ale próbowałem tutaj zachować obie cechy w klasie mediatora, a następnie użyć ich, gdziekolwiek jest to wymagane. Więc mogę zapomnieć o uwzględnieniu obu cech, jeśli znowu potrzebuję gdzie indziej (jak opisałem BusinessMan). Chodzi o style kodu. Jeśli chcesz dołączyć bezpośrednio do swojej klasy. możesz to zrobić - masz co do tego absolutną rację.
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;
?>
Mehdi Homeily
źródło
4
Twoja odpowiedź powinna zawierać wyjaśnienie kodu i opis, w jaki sposób rozwiązuje problem.
AbcAeffchen
1
To prawie oczywiste, ale wspaniale byłoby mieć komentarze do kodu.
Heroselohim
teraz, jeśli Ext1 i Ext2 mają funkcję o tej samej nazwie, zawsze będzie wywoływana Ext1 c, nie będzie to w ogóle zależeć od parametrów.
Alice,
8

Aktualnie akceptowana odpowiedź @Franck będzie działać, ale w rzeczywistości nie jest to dziedziczenie wielokrotne, ale instancja potomna klasy zdefiniowanej poza zakresem, jest też __call()skrót - rozważ użycie $this->childInstance->method(args)wszędzie tam, gdzie potrzebujesz metody klasy ExternalClass w klasie „rozszerzonej”.

Dokładna odpowiedź

Nie, nie możesz, odpowiednio, nie, ponieważ instrukcja extendssłowa kluczowego mówi:

Klasa rozszerzona jest zawsze zależna od jednej klasy bazowej, co oznacza, że ​​dziedziczenie wielokrotne nie jest obsługiwane.

Prawdziwa odpowiedź

Jednak, jak poprawnie zasugerował @adam, NIE zabrania to używania wielokrotnego dziedziczenia hierarchicznego.

MOŻESZ rozszerzyć jedną klasę o inną, o kolejną i tak dalej ...

Takim prostym przykładem byłoby:

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

Ważna uwaga

Jak pewnie zauważyłeś, możesz wykonywać wielokrotne (2+) intehritance przez hierarchię tylko wtedy, gdy masz kontrolę nad wszystkimi klasami wchodzącymi w skład procesu - oznacza to, że nie możesz zastosować tego rozwiązania np. Z klasami wbudowanymi lub z klasami, które po prostu nie można edytować - jeśli chcesz to zrobić, pozostaje rozwiązanie @Franck - instancje podrzędne.

... I na koniec przykład z wynikami:

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

Które wyjścia

I am a of A 
I am b of B 
I am c of C 
jave.web
źródło
6

Przeczytałem kilka artykułów odradzających dziedziczenie w projektach (w przeciwieństwie do bibliotek / frameworków) i zachęcających do programowania interfejsów agaisnt, nie przeciwko implementacjom.
Popierają również OO ze względu na skład: jeśli potrzebujesz funkcji w klasie a i b, spraw, aby c miał elementy / pola tego typu:

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();
    }
}
PhiLho
źródło
To faktycznie staje się tym samym, co pokazują Franck i Sam. Oczywiście, jeśli zdecydujesz się jawnie użyć kompozycji, powinieneś użyć wstrzykiwania zależności .
MikeSchinkel
3

Dziedziczenie wielokrotne wydaje się działać na poziomie interfejsu. Zrobiłem test na php 5.6.1.

Oto działający kod:

<?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

Nie sądziłem, że to możliwe, ale natknąłem się na kod źródłowy SwiftMailer, w klasie Swift_Transport_IoBuffer, która ma następującą definicję:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Jeszcze się tym nie bawiłem, ale pomyślałem, że podzielenie się nim może być interesujące.

molwa
źródło
1
interesujące i nieistotne.
Yevgeniy Afanasjew
1

Właśnie rozwiązałem mój problem z dziedziczeniem wielokrotnym:

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

}

W ten sposób mam możliwość manipulowania sesją wewnątrz SessionResponse, która rozszerza MyServiceResponsetype o możliwość samodzielnej obsługi sesji.

blomman9
źródło
1

Jeśli chcesz sprawdzić, czy funkcja jest publiczna, zajrzyj do tego tematu: https://stackoverflow.com/a/4160928/2226755

I użyj metody call_user_func_array (...) dla wielu argumentów lub nie.

Lubię to :

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");
A-312
źródło
0

PHP nie obsługuje jeszcze dziedziczenia wielu klas, obsługuje jednak dziedziczenie wielu interfejsów.

Zobacz http://www.hudzilla.org/php/6_17_0.php po kilka przykładów.

Tader
źródło
Jeszcze? Wątpię, czy to zrobią. Nowoczesne OO zniechęcają do wielokrotnego dziedziczenia, może być bałagan.
PhiLho
0

PHP nie pozwala na wielokrotne dziedziczenie, ale możesz to zrobić, implementując wiele interfejsów. Jeśli implementacja jest „ciężka”, podaj szkieletową implementację dla każdego interfejsu w osobnej klasie. Następnie możesz delegować wszystkie klasy interfejsu do tych szkieletowych implementacji poprzez zawieranie obiektów .

Piotr K.
źródło
0

Nie wiedząc dokładnie, co próbujesz osiągnąć, sugerowałbym przyjrzenie się możliwości przeprojektowania aplikacji tak, aby w tym przypadku używała kompozycji zamiast dziedziczenia.

Joel
źródło
0

Zawsze dobrym pomysłem jest stworzenie klasy nadrzędnej z funkcjami ... tj. Dodanie tej całej funkcjonalności do klasy rodzica.

I „przenieś” wszystkie klasy, które używają tego hierarchicznie w dół. Potrzebuję - przepisanie funkcji, które są specyficzne.

publikz.com
źródło
0

Jednym z problemów PHP jako języka programowania jest fakt, że możesz mieć tylko jedno dziedzictwo. Oznacza to, że klasa może dziedziczyć tylko z jednej innej klasy.

Jednak często warto dziedziczyć z wielu klas. Na przykład może być pożądane, aby dziedziczyć metody z kilku różnych klas, aby zapobiec duplikowaniu kodu.

Ten problem może prowadzić do tego, że klasa ma długą rodzinną historię dziedziczenia, co często nie ma sensu.

W PHP 5.4 dodano nową funkcję języka znaną jako Cechy. Cecha jest czymś w rodzaju Mixina, ponieważ pozwala na mieszanie klas cech z istniejącą klasą. Oznacza to, że możesz zredukować powielanie kodu i uzyskać korzyści, unikając jednocześnie problemów związanych z wielokrotnym dziedziczeniem.

Cechy

Osama
źródło
-1

To nie jest prawdziwa odpowiedź, mamy zduplikowaną klasę

... ale działa :)

class A {
    //do some things
}

class B {
    //do some things
}

klasa copy_B to kopia całej klasy B.

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

teraz

class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{}  //extends A & B
Koder MR
źródło
-3

klasa A rozszerza B {}

klasa B rozszerza C {}

Następnie A przedłużył zarówno B, jak i C.

Robert DC
źródło
2
@rostamiani, ponieważ ta metoda również modyfikuje klasę B. W większości przypadków chcemy mieć dwie niepowiązane ze sobą klasy Bi C(prawdopodobnie z różnych bibliotek) i być jednocześnie dziedziczonymi przez Atakie, które Azawierają wszystko z obu Bi C, ale Bnie zawierają niczego z Ci na odwrót.
SOFe