PHP에서 둘 이상의 클래스를 사용하여 클래스를 확장 할 수 있습니까?


150

필요한 기능을 갖춘 클래스가 여러 개 있지만 조직을 위해 별도로 저장하려는 경우 클래스를 확장하여 둘 다 가질 수 있습니까?

class a extends b extends c

편집 : 클래스를 한 번에 하나씩 확장하는 방법을 알고 있지만 여러 기본 클래스를 사용하여 클래스를 즉시 확장하는 방법을 찾고 있습니다. class c extends b,class b extends a


집계 또는 인터페이스를 사용하십시오. PHP에는 다중 상속이 없습니다.
Franck

2
큰 클래스 계층 구조의 팬이 아니기 때문에 인터페이스를 살펴보고 있습니다. 그러나 인터페이스가 실제로 어떻게 작동하는지 볼 수 없습니까?
atomicharri

인터페이스를 사용하면 함수 본문이 아닌 API 만 "상속"할 수 있습니다. 클래스 a가 인터페이스 b와 c의 메소드를 강제로 실행하도록합니다. 즉, 동작을 상속하려면 클래스 a에서 클래스 b와 c의 멤버 객체를 집계해야합니다.
Franck

2
Decorators sourcemaking.com/design_patterns/decorator 또는 전략 사용을 고려하십시오 sourcemaking.com/design_patterns/strategy
Gordon

2
특성을 정답으로 고려하십시오.
Daniel

답변:


170

편집 답변 :

다중 상속을 가짜로하려면 마술 함수 __call ()을 사용할 수 있습니다.

이것은 클래스 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");

"abcdef"를 인쇄합니다


1
나는 실제로 수업을 확장한다는 아이디어를 좋아한다. 이런 식으로하는 데 알려진 제한이 있습니까?
atomicharri

3
내가 아는 한 제한은 없지만 PHP는 이와 같은 작은 해킹에 매우 관대 한 언어입니다. :) 다른 사람들이 지적했듯이, 올바른 OOP 방식은 아닙니다.
Franck

64
보호 된 방법이나 개인용 방법을 사용할 수 없습니다.
wormhit

1
@wormhit, 그러나 프로덕션 환경에서 사용하지 않는 것이 좋습니다. ReflectionClass를 사용하여 개인 및 보호 된 메소드에 액세스 할 수 있습니다.
Denis V

2
이것은 구현이 작동하지 않으므로 메소드가 실제로 존재할 것으로 예상하고 여러 기본 클래스에 동일한 이름의 메소드가 있으면 동작이 거의 정의되지 않습니다. 또한 힌트를 줄 수있는 편집자의 능력을 망칠 것입니다.
Erik

138

두 개의 기본 클래스를 확장하는 클래스를 가질 수 없습니다. 당신은 가질 수 없었습니다.

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

그러나 다음과 같은 계층 구조를 가질 수 있습니다 ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

등등.


간호사를 먼저 Matron으로 상속 한 다음 HumanEntity의 상속을 Nurse로 선언하는 것이 왜 옳은지를 설명 할 수 있습니까?
Qwerty

7
@ Qwerty Matron은 간호사의 추가 자질을 가지고 있기 때문에 간호사는 인간의 모든 자질을 가지고 있습니다. 따라서 Matron은 인간 간호사이며 결국 Matron 기능을 가지고 있습니다.
J-Dizzle

58

PHP 5.4에서 사용할 수있는 특성을 사용할 수 있습니다.

특성은 PHP와 같은 단일 상속 언어에서 코드를 재사용하는 메커니즘입니다. 특성은 개발자가 서로 다른 클래스 계층 구조에있는 여러 독립 클래스에서 메소드 세트를 자유롭게 재사용 할 수있게함으로써 단일 상속의 일부 한계를 줄이기위한 것입니다. 특성과 클래스의 조합의 의미는 복잡성을 줄이고 다중 상속 및 믹스 인과 관련된 일반적인 문제를 피하는 방식으로 정의됩니다.

그들은 더 나은 구성과 재사용을 지원할 수있는 잠재력으로 인식되어 Perl 6, Squeak, Scala, Slate 및 Fortress와 같은 최신 버전의 언어로 통합됩니다. 특성도 Java 및 C #으로 이식되었습니다.

자세한 정보 : https://wiki.php.net/rfc/traits


그들을 남용하지 마십시오. 본질적으로 이들은 객체에 적용하는 정적 함수입니다. 당신은 그들을 학대함으로써 혼란을 만들 수 있습니다.
Nikola Petkanski

2
이것이 선택한 답변이 아니라고 믿을 수 없습니다.
다니엘

18

클래스는 단지 메소드의 모음이 아닙니다. 클래스는 상태를 변경하는 상태 (필드)와 동작 (메소드)이 모두있는 추상 개념을 나타내야합니다. 원하는 동작을 얻기 위해 상속을 사용하는 것은 나쁜 OO 디자인과 같으며 많은 언어가 다중 상속을 허용하지 않는 이유와 같습니다. 100 개의 메서드와 20 개의 필드를 상속하지만 5 개만 사용하는 클래스


1
OP의 요청이 "나쁜 OO 디자인"을 구성한다는 귀하의 주장과 관련하여 문제를 제기하겠습니다 . 여러 소스의 클래스에 메소드를 추가하는 것이 건축 적으로 좋은 아이디어라는 입장을 지원하기 위해 Mixins 의 출현을 살펴보십시오 . 그러나 PHP가 최적의 "디자인"을 달성하기 위해 최적의 언어 기능 세트를 제공하지는 않지만, 대략적인 기능을 사용하는 것이 반드시 나쁜 생각이라는 것을 의미하지는 않습니다. @Franck의 답변을보십시오.
MikeSchinkel

15

믹스 인을 곧 추가 할 계획이 있습니다.

그러나 그때까지는 받아 들인 대답을 따르십시오. "확장 가능"클래스를 만들기 위해 약간 추상화 할 수 있습니다.

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

이 모델에서 "OtherClass"는 $ foo에 대해 '알고 있습니다'. OtherClass에는이 관계를 설정하기 위해 "setExtendee"라는 공용 함수가 있어야합니다. 그런 다음 메소드가 $ foo에서 호출되면 내부적으로 $ foo에 액세스 할 수 있습니다. 그러나 실제 확장 클래스처럼 개인 / 보호 된 메소드 / 변수에 액세스 할 수는 없습니다.


12

특성을 기본 클래스로 사용하십시오. 그런 다음 부모 클래스에서 사용하십시오. 연장하십시오.

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

이제 논리적으로 상속 된 비즈니스 여성과 인간 모두를보십시오.


나는 PHP 5.3에 붙어 있습니다. 특성 지원은 없습니다 : D
Nishchal Gautam

BusinessWoman대신에 특성을 사용하지 BusinessPerson않습니까? 그런 다음 실제 다중 상속을 가질 수 있습니다.
SOFe

@SOFe. BusinessMan도 확장 할 수 있기 때문입니다. 비즈니스를하는 사람이라면 누구나 비즈니스를 확장하고 자동으로 특성을 얻을 수 있습니다.
Alice

이 작업을 수행하는 방법은 BusinessPerson이 human이라는 추상 클래스를 확장하는 단일 상속에서 가능한 것과 다르지 않습니다. 내 요점은 당신의 예가 실제로 다중 상속을 필요로하지 않는다는 것입니다.
SOFe

나는 BusinessPerson이 필요할 때까지 믿고, BusinessWoman은 직접 다른 특성을 물려받을 수 있습니다. 그러나 내가 여기서 시도한 것은 두 특성을 중재자 클래스에 유지 한 다음 필요할 때마다 사용하는 것입니다. 그래서 나는 다른 곳에서 필요하다면 두 가지 특성을 모두 포함하는 것을 잊을 수 있습니다 (BusinessMan을 설명했듯이). 코드 스타일에 관한 것입니다. 수업에 직접 포함시키려는 경우. 당신은 그것에 대해 절대적으로 옳은 것처럼 계속 진행할 수 있습니다.
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
답변에는 코드 설명과 문제 해결 방법이 포함되어 있어야합니다.
AbcAeffchen

1
거의 설명이 필요 없지만 코드에 주석을 달면 좋을 것입니다.
Heroselohim

이제 Ext1과 Ext2에 같은 이름의 함수가 항상 Ext1 ccall이라고하는 경우 매개 변수에 전혀 의존하지 않습니다.
앨리스

8

@Franck에 의해 현재 받아 들여진 대답은 효과가 있지만 실제로 다중 상속은 아니지만 범위를 벗어난 클래스의 자식 인스턴스입니다. 또한 __call()속기 가 $this->childInstance->method(args)있습니다. "확장 된"클래스에서 ExternalClass 클래스 메서드가 필요한 곳 ​​어디에서나 사용하십시오 .

정확한 답변

키워드 매뉴얼에extends 따르면 실제로는 그렇지 않습니다 .

확장 클래스는 항상 단일 기본 클래스에 의존합니다. 즉 다중 상속은 지원되지 않습니다.

실제 답변

그러나 @adam이 올바르게 제안한 것처럼 다중 계층 상속을 사용하는 것은 금지되지 않습니다.

한 클래스를 다른 클래스와 다른 클래스로 확장 할 수 있습니다.

이에 대한 간단한 예는 다음과 같습니다.

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

중요 사항

알다시피 , 프로세스에 포함 된 모든 클래스를 제어 할 수있는 경우 계층 구조로만 여러 (2+) intehritance 만 수행 할 수 있습니다. 즉, 내장 클래스 또는 클래스를 사용하여이 솔루션을 적용 할 수 없습니다. 단순히 편집 할 수 없습니다-그렇게하고 싶다면 @Franck 솔루션-자식 인스턴스가 남아 있습니다.

... 그리고 마지막으로 일부 출력 예제 :

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

어떤 출력

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

6

(라이브러리 / 프레임 워크와 달리) 프로젝트의 상속을 권장하지 않으며 구현에 대한 것이 아니라 agaisnt 인터페이스를 프로그래밍하도록 장려하는 여러 기사를 읽었습니다.
그들은 또한 구성에 따라 OO를 옹호합니다 : 클래스 a와 b의 함수가 필요하다면 c 가이 유형의 멤버 / 필드를 갖도록하십시오.

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

이것은 사실상 Franck와 Sam이 보여주는 것과 같은 것이됩니다. 물론 컴포지션을 명시 적으로 사용하기로 선택한 경우 종속성 주입 을 사용해야합니다 .
MikeSchinkel

3

다중 상속은 인터페이스 수준에서 작동하는 것 같습니다. PHP 5.6.1에서 테스트했습니다.

작동 코드는 다음과 같습니다.

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

나는 그것이 가능하다고 생각하지 않았지만 SwiftMailer 소스 코드, Swift_Transport_IoBuffer 클래스에서 우연히 발견되었습니다.

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

나는 아직 그것을 가지고 놀지 않았지만 공유하는 것이 흥미로울 것이라고 생각했습니다.


1
흥미롭고 관련이 없습니다.
Yevgeniy Afanasyev

1

방금 "다중 상속"문제를 해결했습니다.

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

}

이 방법으로 SessionResponse 내에서 세션을 조작하여 MyServiceResponsetype을 확장하여 세션을 자체적으로 처리 할 수 ​​있습니다.


1

함수가 공개인지 확인하려면 다음 주제를 참조하십시오. https://stackoverflow.com/a/4160928/2226755

그리고 많은 인수에 대한 call_user_func_array (...) 메소드를 사용하십시오.

이처럼 :

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는 다중 상속을 허용하지 않지만 다중 인터페이스를 구현하여 할 수 있습니다. 구현이 "무거운" 경우 별도의 클래스에서 각 인터페이스에 대한 골격 구현 을 제공하십시오 . 그런 다음 객체 포함을 통해 모든 인터페이스 클래스를 이러한 골격 구현에 위임 할 수 있습니다 .


0

정확히 무엇을 달성하려고하는지 모르는 경우이 경우 상속보다는 컴포지션을 사용하도록 응용 프로그램을 다시 디자인 할 가능성을 조사해보십시오.


0

항상 좋은 생각은 함수를 사용하여 부모 클래스를 만드는 것입니다. 즉,이 모든 기능을 부모에게 추가하십시오.

그리고 이것을 계층 적으로 사용하는 모든 클래스를 "이동"하십시오. 필자는 특정 기능을 다시 작성해야합니다.


0

프로그래밍 언어로서 PHP의 문제점 중 하나는 단일 상속 만 가질 수 있다는 사실입니다. 이것은 클래스가 다른 클래스에서만 상속 할 수 있음을 의미합니다.

그러나 많은 시간을 여러 클래스에서 상속하는 것이 좋습니다. 예를 들어, 코드 복제를 방지하기 위해 두 개의 다른 클래스에서 메소드를 상속하는 것이 바람직 할 수 있습니다.

이 문제는 가족의 상속 상속 역사가 긴 수업으로 이어질 수 있습니다.

PHP 5.4에서는 언어의 새로운 기능이 속성으로 추가되었습니다. 특성은 특성 클래스를 기존 클래스에 혼합 할 수 있다는 점에서 믹스 인과 유사합니다. 즉, 다중 상속 문제를 피하면서 코드 중복을 줄이고 이점을 얻을 수 있습니다.

특성


-1

이것은 실제 답변이 아닙니다. 중복 수업이 있습니다.

...하지만 작동합니다 :)

class A {
    //do some things
}

class B {
    //do some things
}

copy_B 클래스는 모든 클래스 B를 복사합니다.

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

지금

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

-3

클래스 A는 B를 확장합니다 {}

클래스 B는 C {}를 확장

그런 다음 A는 B와 C를 모두 확장했습니다.


2
이 메소드는 클래스를 수정하기 때문에 @rostamiani B. 우리가 대부분의 시간을 원하는 것은 두 가지 관련이없는 클래스 것입니다 BC(아마 다른 라이브러리에서) 동시에 상속받을 A, 그러한 A모두에서 모든 것을 포함 B하고 C있지만 B로부터 아무것도 포함하지 않는 C반대 및 부사장을.
SOFe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.