Tôi có thể mở rộng một lớp bằng cách sử dụng nhiều hơn 1 lớp trong PHP không?


150

Nếu tôi có một vài lớp với các chức năng mà tôi cần nhưng muốn lưu trữ riêng cho tổ chức, tôi có thể mở rộng một lớp để có cả hai không?

I E class a extends b extends c

chỉnh sửa: Tôi biết cách mở rộng từng lớp một, nhưng tôi đang tìm kiếm một phương pháp để mở rộng ngay lập tức một lớp bằng nhiều lớp cơ sở - AFAIK bạn không thể làm điều này trong PHP nhưng nên có cách nào đó mà không cần dùng đến class c extends b,class b extends a


Sử dụng tổng hợp hoặc giao diện. Nhiều kế thừa không tồn tại trong PHP.
Franck

2
Tôi đang xem xét các giao diện vì tôi không phải là một fan hâm mộ lớn của hệ thống phân cấp lớp lớn. Nhưng tôi không thể thấy giao diện thực sự làm gì?
Atomicharri

Các giao diện cho phép bạn "kế thừa" API, không phải các cơ quan chức năng. Nó buộc lớp a thực hiện các phương thức từ giao diện b và c. Điều đó có nghĩa là nếu bạn muốn kế thừa hành vi, bạn phải tổng hợp các đối tượng thành viên của lớp b và c trong lớp của bạn a.
Franck

2
Cân nhắc sử dụng Công cụ trang trí sourcemaking.com/design_potypes/decorator hoặc Strategies sourcemaking.com/design_potypes/strargety
Gordon

2
Hãy xem xét các đặc điểm là câu trả lời chính xác.
Daniel

Câu trả lời:


170

Trả lời chỉnh sửa của bạn:

Nếu bạn thực sự muốn giả mạo nhiều kế thừa, bạn có thể sử dụng hàm ma thuật __call ().

Điều này là xấu mặc dù nó hoạt động theo quan điểm của người dùng lớp 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");

In "abcdef"


1
Tôi thực sự khá thích ý tưởng mở rộng lớp học Có bất kỳ hạn chế nào được biết khi thực hiện theo cách này không?
Atomicharri

3
Không có giới hạn theo như tôi biết, PHP là một ngôn ngữ rất dễ dãi cho những vụ hack nhỏ như thế này. :) Như những người khác đã chỉ ra, đó không phải là cách OOP thích hợp để làm điều đó.
Franck

64
Bạn sẽ không thể sử dụng các phương pháp được bảo vệ hoặc riêng tư.
wormhit

1
@wormhit, tuy nhiên, tôi không khuyến nghị sử dụng nó trong sản xuất, người ta có thể sử dụng ReflectionClass để truy cập các phương thức riêng tư và được bảo vệ.
Denis V

2
Điều này sẽ không hoạt động đối với Implements, dự kiến ​​phương thức này thực sự tồn tại và hành vi gần như không được xác định khi nhiều lớp cơ sở có một phương thức có cùng tên. Nó cũng sẽ làm rối tung khả năng biên tập của bạn để đưa ra gợi ý cho bạn.
Erik

138

Bạn không thể có một lớp mở rộng hai lớp cơ sở. Bạn không thể có.

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

Tuy nhiên, bạn có thể có một hệ thống phân cấp như sau ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

và như thế.


Bạn có thể giải thích lý do tại sao trước tiên quyền thừa kế Nurse vào Matron, sau đó tuyên bố thừa kế HumanEntity thành Nurse?
Qwerty

7
@Qwerty Bởi vì Matron có những phẩm chất bổ sung của một Y tá, trong khi một y tá có tất cả những phẩm chất của một con người. Do đó, Matron là một y tá người và cuối cùng có khả năng Matron
J-Dizzle

58

Bạn có thể sử dụng các đặc điểm, mà, hy vọng, sẽ có sẵn từ PHP 5.4.

Traits là một cơ chế để tái sử dụng mã trong các ngôn ngữ kế thừa đơn lẻ như PHP. Một Trait nhằm giảm một số hạn chế của thừa kế đơn bằng cách cho phép nhà phát triển sử dụng lại các bộ phương thức một cách tự do trong một số lớp độc lập sống trong các hệ thống phân cấp lớp khác nhau. Các ngữ nghĩa của sự kết hợp giữa Đặc điểm và các lớp được định nghĩa theo cách làm giảm độ phức tạp và tránh các vấn đề điển hình liên quan đến nhiều kế thừa và Mixins.

Họ được công nhận về tiềm năng của họ trong việc hỗ trợ sáng tác và tái sử dụng tốt hơn, do đó họ tích hợp vào các phiên bản mới hơn của các ngôn ngữ như Perl 6, Squeak, Scala, Slate và Fortress. Các đặc điểm cũng đã được chuyển sang Java và C #.

Thêm thông tin: https://wiki.php.net/rfc/traits


Hãy chắc chắn rằng không lạm dụng chúng. Về cơ bản chúng là các hàm tĩnh mà bạn áp dụng cho các đối tượng. Bạn có thể tạo ra một mớ hỗn độn bằng cách lạm dụng chúng.
Nikola Petkanski

2
Tôi không thể tin rằng đây không phải là câu trả lời được chọn.
Daniel

18

Các lớp không có nghĩa chỉ là tập hợp các phương thức. Một lớp được cho là đại diện cho một khái niệm trừu tượng, với cả trạng thái (trường) và hành vi (phương thức) làm thay đổi trạng thái. Sử dụng kế thừa chỉ để có được một số hành vi mong muốn nghe có vẻ như thiết kế OO tồi và chính xác lý do tại sao nhiều ngôn ngữ không cho phép thừa kế nhiều lần: để ngăn chặn "kế thừa spaghetti", tức là mở rộng 3 lớp vì mỗi lớp có một phương thức bạn cần và kết thúc bằng một lớp kế thừa 100 phương thức và 20 trường, nhưng chỉ sử dụng 5 trong số chúng.


1
Tôi sẽ đưa ra vấn đề với khẳng định của bạn rằng yêu cầu của OP cấu thành "thiết kế OO xấu" ; Chỉ cần nhìn vào sự xuất hiện của Mixins để hỗ trợ vị trí thêm các phương thức vào một lớp từ nhiều nguồn là một ý tưởng tốt về mặt kiến ​​trúc. Tuy nhiên tôi sẽ cung cấp cho bạn rằng PHP không cung cấp một bộ tính năng ngôn ngữ tối ưu để đạt được một "thiết kế" tối ưu nhưng điều đó không có nghĩa là sử dụng các tính năng có sẵn để ước tính nó thực sự là một ý tưởng tồi; chỉ cần nhìn vào câu trả lời của @ Franck.
MikeSchinkel

15

Có kế hoạch để thêm hỗn hợp sớm, tôi tin.

Nhưng cho đến lúc đó, đi với câu trả lời được chấp nhận. Bạn có thể tóm tắt một chút để tạo một lớp "có thể mở rộng":

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

Lưu ý rằng trong mô hình này "OtherClass" được 'biết' về $ foo. OtherClass cần có một chức năng công khai gọi là "setExtendee" để thiết lập mối quan hệ này. Sau đó, nếu các phương thức của nó được gọi từ $ foo, nó có thể truy cập $ foo trong nội bộ. Tuy nhiên, nó sẽ không có quyền truy cập vào bất kỳ phương thức / biến riêng tư / được bảo vệ nào như một lớp mở rộng thực sự.


12

Sử dụng các đặc điểm như các lớp cơ sở. Sau đó sử dụng chúng trong một lớp cha. Mở rộng nó .

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

Xem ngay nữ doanh nhân được thừa hưởng hợp lý cả doanh nghiệp và con người;


Tôi bị mắc kẹt với php 5.3 không có hỗ trợ đặc điểm nào: D
Nishchal Gautam

Tại sao không sử dụng đặc điểm trong BusinessWomanthay vì BusinessPerson? Sau đó, bạn có thể có đa thừa kế thực tế.
SOFe

@SOFe, vì BusinessMan cũng có thể mở rộng nó. Vì vậy, ai đã từng làm kinh doanh có thể mở rộng nhân viên busines và có những đặc điểm tự động
Alice

Cách bạn đang làm điều này không có gì khác biệt so với những gì có thể có trong thừa kế duy nhất nơi BusinessPerson mở rộng một lớp trừu tượng gọi là con người. Quan điểm của tôi là ví dụ của bạn không thực sự cần đa kế thừa.
SOFe

Tôi tin rằng cho đến khi BusinessPerson được yêu cầu, BusinessWoman có thể được thừa hưởng trực tiếp các đặc điểm của nó. Nhưng những gì tôi đã cố gắng ở đây là, giữ cả hai đặc điểm trong một lớp trung gian và sau đó sử dụng nó, khi cần thiết. Vì vậy, tại tôi có thể quên về việc bao gồm cả hai đặc điểm nếu một lần nữa cần một số nơi khác (theo như tôi đã mô tả BusinessMan). Đó là tất cả về phong cách mã. Nếu bạn muốn bao gồm trực tiếp đến lớp học của bạn. bạn có thể tiếp tục với điều đó - vì bạn hoàn toàn đúng về điều đó.
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
Câu trả lời của bạn nên chứa phần giải thích về mã của bạn và mô tả cách giải quyết vấn đề.
AbcAeffchen

1
Nó gần như tự giải thích, nhưng sẽ rất tuyệt nếu có ý kiến ​​dọc theo mã.
Heroselohim

Bây giờ nếu cả Ext1 và Ext2 đều có một hàm có cùng tên luôn thì Ext1 sẽ được gọi, nó sẽ không phụ thuộc vào tham số nào cả.
Alice

8

Câu trả lời hiện được chấp nhận bởi @Franck sẽ hoạt động nhưng thực tế nó không phải là nhiều kế thừa mà là một thể hiện con của lớp được xác định ngoài phạm vi, cũng có __call()cách viết tắt - xem xét sử dụng $this->childInstance->method(args)bất cứ nơi nào bạn cần phương thức lớp InternalClass trong lớp "mở rộng".

Câu trả lời chính xác

Không, bạn không thể, tương ứng, không thực sự, như hướng dẫn của extendstừ khóa nói:

Một lớp mở rộng luôn phụ thuộc vào một lớp cơ sở duy nhất, nghĩa là, nhiều kế thừa không được hỗ trợ.

Câu trả lời thực sự

Tuy nhiên, như @adam đã đề xuất một cách chính xác, điều này KHÔNG cấm bạn sử dụng nhiều kế thừa phân cấp.

Bạn CÓ THỂ mở rộng một lớp, với lớp khác và lớp khác với ... và ...

Vì vậy, ví dụ khá đơn giản về điều này sẽ là:

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

Lưu ý quan trọng

Như bạn có thể nhận thấy, bạn chỉ có thể thực hiện nhiều (2+) tính phân cấp theo phân cấp nếu bạn có quyền kiểm soát tất cả các lớp trong quy trình - điều đó có nghĩa là, bạn không thể áp dụng giải pháp này, ví dụ như với các lớp tích hợp hoặc với các lớp bạn chỉ đơn giản là không thể chỉnh sửa - nếu bạn muốn làm điều đó, bạn sẽ chỉ còn lại giải pháp @Franck - các thể hiện con.

... Và cuối cùng là ví dụ với một số đầu ra:

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

Đầu ra nào

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

6

Tôi đã đọc một số bài viết không khuyến khích sự kế thừa trong các dự án (trái ngược với các thư viện / khung công tác) và khuyến khích lập trình các giao diện khó hiểu, không chống lại việc triển khai.
Họ cũng ủng hộ OO theo thành phần: nếu bạn cần các hàm trong lớp a và b, làm cho c có thành viên / trường thuộc loại này:

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

Điều này thực sự trở thành điều tương tự mà Franck và Sam thể hiện. Tất nhiên, nếu bạn chọn sử dụng thành phần rõ ràng, bạn nên sử dụng tiêm phụ thuộc .
MikeSchinkel

3

Nhiều kế thừa dường như hoạt động ở cấp độ giao diện. Tôi đã thực hiện một thử nghiệm trên php 5.6.1.

Đây là một mã làm việc:

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

Tôi không nghĩ điều đó là có thể, nhưng tôi đã tình cờ tìm thấy trong mã nguồn SwiftMailer, trong lớp Swift_Transport_IoBuffer, có định nghĩa sau:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Tôi chưa chơi với nó, nhưng tôi nghĩ nó có thể thú vị để chia sẻ.


1
thú vị và không liên quan.
Yevgeniy Afanasyev

1

Tôi vừa giải quyết vấn đề "đa thừa kế" của mình với:

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

}

Bằng cách này, tôi có quyền điều khiển phiên bên trong SessionResponse, mở rộng MyServiceResponsetype vẫn có thể tự xử lý Phiên.


1

Nếu bạn muốn kiểm tra xem một chức năng có công khai hay không, hãy xem chủ đề này: https://stackoverflow.com/a/4160928/2226755

Và sử dụng phương thức call_user_func_array (...) cho nhiều đối số hoặc không.

Như thế này :

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 chưa hỗ trợ kế thừa nhiều lớp, tuy nhiên nó hỗ trợ nhiều kế thừa giao diện.

Xem http://www.hudzilla.org/php/6_17_0.php để biết một số ví dụ.


Chưa? Tôi nghi ngờ họ sẽ làm điều đó. OO hiện đại không khuyến khích nhiều kế thừa, nó có thể lộn xộn.
PhiLho

0

PHP không cho phép nhiều kế thừa, nhưng bạn có thể làm với việc thực hiện nhiều giao diện. Nếu việc triển khai là "nặng", hãy cung cấp triển khai bộ xương cho mỗi giao diện trong một lớp riêng biệt. Sau đó, bạn có thể ủy quyền tất cả các lớp giao diện cho các triển khai bộ xương này thông qua việc ngăn chặn đối tượng .


0

Không biết chính xác những gì bạn đang cố gắng đạt được, tôi sẽ đề nghị xem xét khả năng thiết kế lại ứng dụng của bạn để sử dụng thành phần thay vì kế thừa trong trường hợp này.


0

Luôn luôn có ý tưởng tốt là tạo lớp cha, với các hàm ... tức là thêm tất cả chức năng này vào lớp cha.

Và "di chuyển" tất cả các lớp sử dụng thứ bậc này xuống. Tôi cần - viết lại các chức năng, đó là cụ thể.


0

Một trong những vấn đề của PHP là ngôn ngữ lập trình là thực tế là bạn chỉ có thể có sự kế thừa duy nhất. Điều này có nghĩa là một lớp chỉ có thể kế thừa từ một lớp khác.

Tuy nhiên, rất nhiều thời gian sẽ có ích khi kế thừa từ nhiều lớp. Ví dụ, có thể mong muốn kế thừa các phương thức từ một vài lớp khác nhau để ngăn ngừa sao chép mã.

Vấn đề này có thể dẫn đến giai cấp có lịch sử gia đình lâu dài về di sản mà thường không có ý nghĩa.

Trong PHP 5.4, một tính năng mới của ngôn ngữ đã được thêm vào là Traits. Một Trait giống như một Mixin ở chỗ nó cho phép bạn trộn các lớp Trait vào một lớp hiện có. Điều này có nghĩa là bạn có thể giảm trùng lặp mã và nhận được lợi ích trong khi tránh các vấn đề của nhiều kế thừa.

Đặc điểm


-1

Đây không phải là một câu trả lời thực sự, chúng tôi có lớp trùng lặp

...nhưng nó đã có tác dụng :)

class A {
    //do some things
}

class B {
    //do some things
}

lớp copy_B là sao chép tất cả lớp B

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

hiện nay

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

-3

lớp A mở rộng B {}

lớp B mở rộng C {}

Sau đó A đã mở rộng cả B và C


2
@rostamiani vì phương thức này cũng đang sửa đổi lớp B. Điều chúng tôi muốn hầu hết thời gian là có hai lớp không liên quan BC(có thể từ các thư viện khác nhau) và được thừa kế đồng thời A, như vậy Acó chứa mọi thứ từ cả hai BC, nhưng Bkhông chứa bất cứ thứ gì từ Cvà ngược lại.
SOFe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.