Bisakah saya memperluas kelas menggunakan lebih dari 1 kelas di PHP?


150

Jika saya memiliki beberapa kelas dengan fungsi yang saya perlukan tetapi ingin menyimpan secara terpisah untuk organisasi, dapatkah saya memperluas kelas untuk memiliki keduanya?

yaitu class a extends b extends c

sunting: Saya tahu cara memperluas kelas satu per satu, tetapi saya sedang mencari metode untuk langsung memperpanjang kelas menggunakan beberapa kelas dasar - AFAIK Anda tidak dapat melakukan ini dalam PHP tetapi harus ada cara di sekitarnya tanpa menggunakan class c extends b,class b extends a


Gunakan agregasi atau antarmuka. Pewarisan berganda tidak ada dalam PHP.
Franck

2
Saya melihat ke antarmuka karena saya bukan penggemar besar hierarki kelas besar. Tetapi saya tidak dapat melihat bagaimana antarmuka benar-benar melakukan sesuatu?
atomicharri

Antarmuka memungkinkan Anda "mewarisi" API saja, bukan badan yang berfungsi. Ini memaksa kelas a untuk mengimplementasikan metode dari antarmuka b dan c. Itu berarti bahwa jika Anda ingin mewarisi perilaku Anda harus mengumpulkan objek anggota kelas b dan c di kelas Anda a.
Franck

2
Pertimbangkan untuk menggunakan Dekorator sourcemaking.com/design_patterns/decorator atau Strategies sourcemaking.com/design_patterns/strategy
Gordon

2
Harap pertimbangkan sifat sebagai jawaban yang benar.
Daniel

Jawaban:


170

Menjawab hasil edit Anda:

Jika Anda benar-benar ingin memalsukan banyak warisan, Anda dapat menggunakan fungsi ajaib __call ().

Ini jelek meskipun bekerja dari sudut pandang pengguna kelas 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");

Mencetak "abcdef"


1
Saya sebenarnya cukup menyukai ide untuk memperluas kelas. Adakah batasan yang diketahui untuk melakukannya dengan cara ini?
atomicharri

3
Tidak ada batasan sejauh yang saya tahu, PHP adalah bahasa yang sangat permisif untuk peretasan kecil seperti ini. :) Seperti yang telah ditunjukkan orang lain, itu bukan cara OOP yang tepat untuk melakukannya.
Franck

64
Anda tidak akan dapat menggunakan metode yang dilindungi atau pribadi.
Cacing

1
@wormhit, meskipun, saya tidak akan merekomendasikannya untuk digunakan dalam produksi, orang dapat menggunakan ReflectionClass untuk mengakses metode pribadi dan dilindungi.
Denis V

2
Ini tidak akan berfungsi untuk Implements, yang mengharapkan metode untuk benar-benar ada dan perilaku cukup banyak ditentukan ketika beberapa kelas dasar memiliki metode dengan nama yang sama. Ini juga akan mengacaukan kemampuan editor Anda untuk memberi Anda petunjuk.
Erik

138

Anda tidak dapat memiliki kelas yang memperpanjang dua kelas dasar. Anda tidak bisa.

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

Namun Anda dapat memiliki hierarki sebagai berikut ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

dan seterusnya.


Bisakah Anda menjelaskan mengapa mewarisi Perawat ke Matron terlebih dahulu, lalu mendeklarasikan pewarisan HumanEntity ke Perawat?
Qwerty

7
@Qwerty Karena Matron memiliki kualitas tambahan sebagai Perawat, sementara seorang perawat memiliki semua kualitas manusia. Oleh karena itu, Matron adalah seorang perawat manusia dan akhirnya memiliki kemampuan Matron
J-Dizzle

58

Anda dapat menggunakan ciri-ciri, yang, semoga, akan tersedia dari PHP 5.4.

Ciri adalah mekanisme untuk menggunakan kembali kode dalam bahasa pewarisan tunggal seperti PHP. Trait dimaksudkan untuk mengurangi beberapa batasan pewarisan tunggal dengan memungkinkan pengembang untuk menggunakan kembali set metode secara bebas di beberapa kelas independen yang hidup dalam hierarki kelas yang berbeda. Semantik dari kombinasi Ciri dan kelas didefinisikan dengan cara, yang mengurangi kompleksitas dan menghindari masalah khas yang terkait dengan pewarisan berganda dan Mixin.

Mereka dikenal karena potensi mereka dalam mendukung komposisi dan penggunaan kembali yang lebih baik, karenanya integrasi mereka dalam versi bahasa yang lebih baru seperti Perl 6, Squeak, Scala, Slate dan Fortress. Ciri juga telah porting ke Jawa dan C #.

Informasi lebih lanjut: https://wiki.php.net/rfc/traits


Pastikan untuk tidak menyalahgunakannya. Pada dasarnya mereka adalah fungsi statis yang Anda terapkan ke objek. Anda dapat membuat kekacauan dengan menyalahgunakan mereka.
Nikola Petkanski

2
Saya tidak percaya ini bukan jawaban yang dipilih.
Daniel

18

Kelas tidak hanya dimaksudkan sebagai kumpulan metode. Kelas seharusnya mewakili konsep abstrak, dengan kedua negara (bidang) dan perilaku (metode) yang mengubah negara. Menggunakan pewarisan hanya untuk mendapatkan beberapa perilaku yang diinginkan terdengar seperti desain OO yang buruk, dan justru alasan mengapa banyak bahasa melarang pewarisan berganda: untuk mencegah "pewarisan spaghetti", yaitu memperluas 3 kelas karena masing-masing memiliki metode yang Anda butuhkan, dan berakhir dengan kelas yang mewarisi 100 metode dan 20 bidang, namun hanya menggunakan 5 di antaranya.


1
Saya akan mempermasalahkan pernyataan Anda bahwa permintaan OP merupakan "desain OO buruk" ; lihat saja kemunculan Mixin untuk mendukung posisi bahwa menambahkan metode ke kelas dari berbagai sumber adalah ide bagus secara arsitektur. Namun saya akan memberi Anda bahwa PHP tidak menyediakan serangkaian fitur bahasa yang optimal untuk mencapai "desain" yang optimal, tetapi itu tidak berarti menggunakan fitur yang tersedia untuk memperkirakannya tentu merupakan ide yang buruk; lihat saja jawaban @ Franck.
MikeSchinkel

15

Saya yakin ada rencana untuk menambahkan mix-in segera.

Tetapi sampai saat itu, pergilah dengan jawaban yang diterima. Anda bisa abstrak sedikit untuk membuat kelas "extended":

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

Perhatikan bahwa dalam model ini "OtherClass" dapat 'mengetahui' tentang $ foo. OtherClass perlu memiliki fungsi publik yang disebut "setExtendee" untuk mengatur hubungan ini. Kemudian, jika metode itu dipanggil dari $ foo, ia dapat mengakses $ foo secara internal. Namun, itu tidak akan mendapatkan akses ke metode / variabel pribadi / terlindungi seperti yang akan diperluas oleh kelas nyata.


12

Gunakan sifat sebagai kelas dasar. Kemudian gunakan di kelas induk. Perpanjang itu.

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

Lihat sekarang wanita bisnis yang secara logis mewarisi bisnis dan manusia;


Saya terjebak dengan php 5.3 tidak ada dukungan sifat: D
Nishchal Gautam

Mengapa tidak menggunakan sifat itu BusinessWomansebagai ganti BusinessPerson? Maka Anda dapat memiliki multi-warisan yang sebenarnya.
SOFe

@ SOFe, karena BusinessMan juga dapat memperluasnya. Jadi siapa yang pernah melakukan bisnis dapat memperluas bisnis orang, dan mendapatkan sifat secara otomatis
Alice

Cara Anda melakukan ini tidak ada bedanya dengan apa yang mungkin dalam satu warisan di mana BusinessPerson memperluas kelas abstrak yang disebut manusia. Maksud saya adalah bahwa contoh Anda tidak benar-benar membutuhkan multi pewarisan.
SOFe

Saya percaya sampai BusinessPerson itu diperlukan, BusinessWoman bisa secara langsung mewarisi sifat-sifat lain. Tapi yang saya coba di sini adalah, menjaga kedua sifat di kelas mediator dan kemudian menggunakannya, di mana pun diperlukan. Jadi pada saya bisa melupakan tentang menyertakan kedua sifat jika lagi membutuhkan beberapa tempat lain (seperti yang saya jelaskan BusinessMan). Ini semua tentang gaya kode. Jika Anda ingin memasukkan langsung ke kelas Anda. Anda dapat melanjutkan dengan itu - karena Anda benar tentang itu.
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
Jawaban Anda harus berisi penjelasan tentang kode Anda dan deskripsi bagaimana memecahkan masalah.
AbcAeffchen

1
Ini hampir jelas, tetapi akan bagus untuk memiliki komentar di sepanjang kode.
Heroselohim

sekarang jika Ext1 dan Ext2 keduanya memiliki fungsi dengan nama yang sama selalu Ext1 c akan dipanggil, itu tidak akan bergantung pada parameter sama sekali.
Alice

8

Jawaban yang saat ini diterima oleh @Franck akan bekerja tetapi sebenarnya bukan multiple inheritance tetapi turunan anak dari kelas yang didefinisikan di luar ruang lingkup, juga terdapat __call()singkatan - pertimbangkan untuk menggunakan di $this->childInstance->method(args)mana saja Anda memerlukan metode kelas ExternalClass di kelas "extended".

Jawaban yang tepat

Tidak, Anda tidak dapat, masing-masing, tidak benar-benar, seperti yang dikatakan oleh kata kunci manualextends :

Kelas yang diperluas selalu tergantung pada kelas basis tunggal, yaitu, multiple inheritance tidak didukung.

Jawaban nyata

Namun seperti yang disarankan @adam dengan benar, ini TIDAK melarang Anda untuk menggunakan banyak pewarisan hierarki.

Anda BISA memperpanjang satu kelas, dengan yang lain dan yang lain dengan yang lain dan seterusnya ...

Contoh yang cukup sederhana untuk ini adalah:

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

Catatan penting

Seperti yang mungkin telah Anda perhatikan, Anda hanya dapat melakukan beberapa (2+) intehritance berdasarkan hierarki jika Anda memiliki kendali atas semua kelas yang termasuk dalam proses - itu artinya, Anda tidak dapat menerapkan solusi ini misalnya dengan kelas bawaan atau dengan kelas yang Anda miliki. tidak dapat mengedit - jika Anda ingin melakukan itu, Anda dibiarkan dengan solusi @Franck - instance anak.

... Dan akhirnya contoh dengan beberapa 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();

Output yang mana

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

6

Saya telah membaca beberapa artikel yang mengecilkan warisan dalam proyek-proyek (tidak seperti perpustakaan / kerangka kerja), dan mendorong untuk memprogram antarmuka antarmuka, tidak menentang implementasi.
Mereka juga menganjurkan OO dengan komposisi: jika Anda memerlukan fungsi di kelas a dan b, buat c memiliki anggota / bidang seperti ini:

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

Ini secara efektif menjadi hal yang sama yang ditunjukkan oleh Franck dan Sam. Tentu saja jika Anda memilih untuk secara eksplisit menggunakan komposisi Anda harus menggunakan injeksi ketergantungan .
MikeSchinkel

3

Warisan berganda tampaknya berfungsi pada level antarmuka. Saya membuat tes di php 5.6.1.

Ini adalah kode yang berfungsi:

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

Saya tidak berpikir itu mungkin, tapi saya menemukan kode sumber SwiftMailer, di kelas Swift_Transport_IoBuffer, yang memiliki definisi berikut:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Saya belum bermain dengannya, tapi saya pikir mungkin menarik untuk dibagikan.


1
menarik dan tidak relevan.
Yevgeniy Afanasyev

1

Saya baru saja menyelesaikan masalah "multiple inheritance" saya dengan:

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

}

Dengan cara ini saya memiliki kekuatan untuk memanipulasi sesi di dalam SessionResponse yang meluas MyServiceResponsetype masih bisa menangani Session dengan sendirinya.


1

Jika Anda ingin memeriksa apakah suatu fungsi bersifat publik, lihat topik ini: https://stackoverflow.com/a/4160928/2226755

Dan gunakan metode call_user_func_array (...) untuk banyak atau tidak argumen.

Seperti ini :

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 belum mendukung multiple class inheritance, namun ia mendukung multiple interface inheritance.

Lihat http://www.hudzilla.org/php/6_17_0.php untuk beberapa contoh.


Namun? Saya ragu mereka akan melakukannya. OO modern mencegah banyak warisan, bisa jadi berantakan.
PhiLho

0

PHP tidak mengizinkan banyak pewarisan, tetapi Anda dapat melakukannya dengan mengimplementasikan banyak antarmuka. Jika implementasinya "berat", berikan implementasi kerangka untuk setiap antarmuka dalam kelas yang terpisah. Kemudian, Anda bisa mendelegasikan semua kelas antarmuka ke implementasi kerangka ini melalui penahanan objek .


0

Tidak tahu persis apa yang ingin Anda capai, saya sarankan melihat kemungkinan mendesain ulang aplikasi Anda untuk menggunakan komposisi daripada warisan dalam kasus ini.


0

Gagasan yang selalu baik adalah membuat kelas induk, dengan fungsi ... yaitu menambahkan semua fungsi ini ke induk.

Dan "pindahkan" semua kelas yang menggunakan ini secara hierarkis ke bawah. Saya perlu - menulis ulang fungsi, yang spesifik.


0

Salah satu masalah PHP sebagai bahasa pemrograman adalah kenyataan bahwa Anda hanya dapat memiliki satu warisan. Ini berarti suatu kelas hanya dapat mewarisi dari satu kelas lain.

Namun, banyak waktu akan bermanfaat untuk mewarisi dari beberapa kelas. Misalnya, mungkin diinginkan untuk mewarisi metode dari beberapa kelas yang berbeda untuk mencegah duplikasi kode.

Masalah ini dapat menyebabkan kelas yang memiliki sejarah keluarga yang panjang tentang warisan yang seringkali tidak masuk akal.

Dalam PHP 5.4 fitur baru bahasa ditambahkan dikenal sebagai Ciri. Trait adalah jenis seperti Mixin karena memungkinkan Anda untuk mencampur kelas Trait menjadi kelas yang ada. Ini berarti Anda dapat mengurangi duplikasi kode dan mendapatkan manfaat sambil menghindari masalah pewarisan berganda.

Sifat


-1

Ini bukan jawaban nyata, kami memiliki kelas duplikat

... tapi berhasil :)

class A {
    //do some things
}

class B {
    //do some things
}

kelas copy_B adalah salin semua kelas B

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

sekarang

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

-3

kelas A meluas B {}

kelas B meluas C {}

Kemudian A telah memperpanjang B dan C


2
@rostamiani karena metode ini juga memodifikasi kelas B. Apa yang kita inginkan sebagian besar waktu adalah memiliki dua kelas yang tidak terkait Bdan C(mungkin dari perpustakaan yang berbeda) dan secara simultan diwariskan oleh A, sehingga Aberisi semuanya dari keduanya Bdan C, tetapi Btidak mengandung apa pun dari Cdan sebaliknya.
SOFe
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.