특성과 인터페이스


344

나는 최근 PHP에 대해 연구하려고 노력해 왔고, 나는 특성에 매달리는 것을 발견했다. 수평 코드 재사용의 개념을 이해하고 반드시 추상 클래스에서 상속 받기를 원하지 않습니다. 내가 이해하지 못하는 것은 특성과 인터페이스를 사용하는 것의 중요한 차이점은 무엇입니까?

적절한 블로그 게시물이나 기사 중 하나를 사용할 때를 설명하는 기사를 검색하려고 시도했지만 지금까지 찾은 예제는 동일한 것으로 보입니다.


6
인터페이스에는 함수 본문에 코드가 없습니다. 실제로는 기능 바디가 없습니다.
hakre

2
나는 많은 대답을 받았음에도 불구하고 나는 일반적으로 anti-trait / mixin 이라는 기록에 대해 언급하고 싶다 . 이 대화 내용을 확인하여 특성이 종종 견고한 OOP 관행을 훼손하는 방법 을 읽으십시오 .
rdlowrey

2
나는 반대로 주장한다. 특성의 출현 이전과 이후 몇 년 동안 PHP로 작업 한 결과, 그 가치를 쉽게 증명할 수 있다고 생각합니다. ' 실제 예 '를 읽어 보면 '이미지 모델'도 Imagick개체 와 같이 걸으며 이야기 할 수 있습니다.
quickshiftin

특성과 인터페이스는 비슷합니다. 주요 차이점은 특성이 인터페이스를 통해 메소드를 구현할 수 있다는 것입니다.
John

답변:


239

인터페이스는 구현 클래스 구현 해야하는 일련의 메소드를 정의 합니다 .

특성이 정의되면 use메소드의 구현도 함께 나타납니다 Interface.

가장 큰 차이점입니다.

PHP RFC수평 재사용에서 :

특성은 PHP와 같은 단일 상속 언어에서 코드를 재사용하는 메커니즘입니다. 특성은 개발자가 서로 다른 클래스 계층 구조에있는 여러 독립 클래스에서 메소드 세트를 자유롭게 재사용 할 수있게함으로써 단일 상속의 일부 한계를 줄이기위한 것입니다.


2
@JREAM 실제로는 아무것도 없습니다. 실제로 훨씬 더.
Alec Gorge

79
특성이 전혀 인터페이스가 아니라는 것을 제외하고. 인터페이스는 확인할 수있는 사양입니다. 특성을 확인할 수 없으므로 구현 만 가능합니다. 인터페이스와 정반대입니다. RFC의 그 라인은 단순히 잘못입니다 ...
ircmaxell

195
특성은 본질적으로 언어 지원 복사 및 붙여 넣기 입니다.
Shahid

10
그것은 은유가 아닙니다. 그것은 단어의 의미를 도살하고 있습니다. 상자를 부피가있는 표면으로 묘사하는 것과 같습니다.
cleong

6
ircmaxell 및 Shadi의 주석을 확장하려면 : 객체가 instanceof를 통해 인터페이스를 구현하는지 여부를 확인하고 메소드 서명이 메소드 힌트의 유형 힌트를 통해 인터페이스를 구현하는지 확인할 수 있습니다. 특성에 대한 해당 검사를 수행 할 수 없습니다.
Brian D' Astous

530

공공 서비스 발표 :

나는 특성이 거의 항상 코드 냄새이며 구성을 위해 피해야한다고 기록에 대해 진술하고 싶습니다. 단일 상속이 반 패턴이되고 여러 상속이이 문제를 복잡하게한다는 점에서 단일 상속이 자주 남용된다고 생각합니다. 대부분의 경우 상속보다 구성을 선호하여 훨씬 더 나은 서비스를 제공 할 수 있습니다 (단일 또는 다중). 특성과 인터페이스와의 관계에 여전히 관심이 있다면 ...


이 말로 시작하자 :

객체 지향 프로그래밍 (OOP)은 파악하기 어려운 패러다임이 될 수 있습니다. 클래스를 사용한다고해서 코드가 객체 지향 (OO)이라는 의미는 아닙니다.

OO 코드를 작성하려면 OOP가 실제로 객체의 기능에 관한 것임을 이해해야합니다. 실제로 하는 것 대신 할 수있는 것의 관점에서 클래스에 대해 생각 해야 합니다. 이것은 약간의 코드를 "무언가로 만드는"것에 중점을 둔 전통적인 절차 적 프로그래밍과는 완전히 대조적이다.

OOP 코드가 계획 및 설계에 관한 것이라면 인터페이스는 청사진이며 객체는 완전히 구성된 집입니다. 한편, 특성은 단순히 청사진 (인터페이스)으로 구성된 집을 짓는 데 도움이되는 방법입니다.

인터페이스

그렇다면 왜 인터페이스를 사용해야합니까? 간단히 말해서 인터페이스는 코드의 취성을 떨어 뜨립니다. 이 진술이 의심 스러우면 인터페이스에 대해 작성되지 않은 레거시 코드를 유지하도록 강요받은 사람에게 문의하십시오.

인터페이스는 프로그래머와 코드 간의 계약입니다. 인터페이스는 "내 규칙에 따라 플레이하는 한 원하는대로 구현할 수 있으며 다른 코드는 중단하지 않겠다고 약속합니다."라고 말합니다.

예를 들어, 실제 시나리오 (자동차 또는 위젯 없음)를 고려하십시오.

웹 애플리케이션이 서버로드를 줄이기 위해 캐싱 시스템을 구현하려고합니다.

APC를 사용하여 요청 응답을 캐시하는 클래스를 작성하여 시작하십시오.

class ApcCacher
{
  public function fetch($key) {
    return apc_fetch($key);
  }
  public function store($key, $data) {
    return apc_store($key, $data);
  }
  public function delete($key) {
    return apc_delete($key);
  }
}

그런 다음 HTTP 응답 오브젝트에서 실제 응답을 생성하기 위해 모든 작업을 수행하기 전에 캐시 적중을 점검하십시오.

class Controller
{
  protected $req;
  protected $resp;
  protected $cacher;

  public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
    $this->req    = $req;
    $this->resp   = $resp;
    $this->cacher = $cacher;

    $this->buildResponse();
  }

  public function buildResponse() {
    if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
      $this->resp = $response;
    } else {
      // Build the response manually
    }
  }

  public function getResponse() {
    return $this->resp;
  }
}

이 방법은 효과적입니다. 그러나 몇 주 후에 APC 대신 파일 기반 캐시 시스템을 사용하기로 결정했습니다. 이제 ApcCacher클래스의 기능을 표현하는 인터페이스가 아닌 클래스 의 기능을 사용하도록 컨트롤러를 프로그래밍 했으므로 컨트롤러 코드를 변경해야합니다 ApcCacher. 위 대신에 당신이 Controller클래스 CacherInterface대신에 콘크리트 대신에 의존하게 만들었다 고 가정 해 봅시다 ApcCacher.

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

이와 함께 인터페이스를 다음과 같이 정의하십시오.

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

그리고 당신은 당신 ApcCacher과 당신의 새로운 FileCacher클래스를 구현 하고 인터페이스에 필요한 기능을 사용하도록 클래스를 CacherInterface프로그래밍합니다 Controller.

이 예제는 인터페이스 프로그래밍이 변경 사항으로 인해 다른 코드가 손상 될 염려없이 클래스의 내부 구현을 변경하는 방법을 보여줍니다.

특성

반면에 특성은 단순히 코드를 재사용하는 방법입니다. 인터페이스는 특성에 대한 상호 배타적 인 대안으로 생각해서는 안됩니다. 실제로 인터페이스에 필요한 기능을 충족시키는 특성을 만드는 것이 이상적인 사용 사례 입니다.

여러 클래스가 동일한 기능을 공유 할 때 (같은 인터페이스에 의해 지시 될 수 있음) 특성 만 사용해야합니다. 특성을 사용하여 단일 클래스에 기능을 제공하는 것은 의미가 없습니다. 이는 클래스의 기능을 난독 화하고 더 나은 디자인은 특성의 기능을 관련 클래스로 옮길 것입니다.

다음 특성 구현을 고려하십시오.

interface Person
{
    public function greet();
    public function eat($food);
}

trait EatingTrait
{
    public function eat($food)
    {
        $this->putInMouth($food);
    }

    private function putInMouth($food)
    {
        // Digest delicious food
    }
}

class NicePerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Good day, good sir!';
    }
}

class MeanPerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Your mother was a hamster!';
    }
}

더 구체적인 예 : 인터페이스 토론에서 귀하 FileCacher와 귀하 ApcCacher의 인터페이스 항목이 동일한 방법을 사용하여 캐시 항목이 오래되어 삭제되어야하는지 여부를 결정 한다고 상상해보십시오 (실제로는 그렇지 않지만 실제로는 마찬가지입니다). 특성을 작성하고 두 클래스 모두 공통 인터페이스 요구 사항에이를 사용할 수 있습니다.

주의해야 할 마지막 말 : 특성으로 배 밖으로 나가지 않도록주의하십시오. 독특한 클래스 구현으로 충분할 때 특성이 열악한 디자인의 버팀목으로 사용되는 경우가 종종 있습니다. 최상의 코드 디자인을 위해서는 특성을 인터페이스 요구 사항을 충족하도록 제한해야합니다.


69
나는 위에 제공된 빠른 간단한 답변을 찾고 있었지만 다른 사람들, 구별을 명확하게하는 데 도움이되는 훌륭한 심층 답변을 제공했다고 말해야합니다.
datguywhowanders

35
"[C] 특정 클래스의 인터페이스에 필요한 기능을 충족시키는 특성을 제거하는 것이 이상적인 사용 사례입니다." 정확히 : +1
Alec Gorge

5
PHP의 특성이 다른 언어의 믹스 인과 비슷하다고 말할 수 있습니까?
Eno

5
@igorpan 모든 의도와 목적을 위해 PHP의 특성 구현 다중 상속과 동일 하다고 말합니다 . PHP의 특성이 정적 속성을 지정하면 해당 특성을 사용하는 각 클래스에는 고유 한 정적 속성의 사본이 있습니다. 더 중요한 것은 ... 특성을 쿼리 할 때 SERP 에서이 게시물이 어떻게 매우 높은 지 보는 것입니다. 페이지 상단에 공공 서비스 공지를 추가 할 것입니다. 읽어야합니다.
rdlowrey

3
자세한 설명은 +1입니다. 나는 믹스 인이 많이 사용되는 루비 배경에서 왔습니다. 내 두 센트를 추가하기 위해 우리가 사용하는 좋은 경험 법칙은 PHP에서 "특성에서 $ this를 변경하는 메소드를 구현하지 마십시오"로 번역 될 수 있습니다. 이것은 미친 디버깅 세션의 전체 무리를 방지합니다 ... mixin은 또한 혼합 될 클래스에 대한 가정을해서는 안됩니다 (또는 매우 명확하게하고 종속성을 최소한으로 줄여야합니다). 이와 관련하여 인터페이스를 구현하는 특성에 대한 아이디어가 좋습니다.
m_x

67

A trait는 본질적으로 PHP의의 구현이며 mixin,의 추가를 통해 모든 클래스에 추가 할 수있는 확장 메소드 세트입니다 trait. 그런 다음 메소드는 상속을 사용하지 않고 해당 클래스 구현의 일부가 됩니다.

보내는 사람 PHP 매뉴얼 (강조 광산) :

특성은 PHP와 같은 단일 상속 언어에서 코드를 재사용 하는 메커니즘입니다 . ... 그것은 전통적인 상속에 추가 된 것이며 행동의 수평 적 구성을 가능하게합니다. 즉, 상속을 요구하지 않고 클래스 멤버를 적용하는 것입니다.

예를 들면 :

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

위의 특성을 정의하면 이제 다음을 수행 할 수 있습니다.

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

내가 클래스의 인스턴스를 만들 때이 시점에서 MyClass, 그것은라는 두 가지 방법이 있습니다 foo()bar()에서 온 - myTrait. 그리고- trait정의 된 메소드에는 이미 Interface정의 된 메소드가 할 수없는 메소드 본문이 있습니다.

또한 PHP는 다른 많은 언어와 마찬가지로 단일 상속 모델을 사용하므로 클래스는 여러 인터페이스가 아닌 여러 인터페이스에서 파생 될 수 있습니다. 그러나 PHP 클래스 에는 여러 기본 클래스가 포함 된 것처럼 프로그래머가 재사용 가능한 부분을 포함 할 있는 여러 trait포함이 포함될 있습니다 .

몇 가지 참고할 사항 :

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

다형성 :

여기서, 이전의 실시 예에서, MyClass 연장 SomeBaseClass , MyClass 인스턴스 SomeBaseClass. 즉,와 같은 배열 SomeBaseClass[] bases은의 인스턴스를 포함 할 수 있습니다 MyClass. 마찬가지로 MyClass확장 된 IBaseInterface경우의 배열 IBaseInterface[] bases에는의 인스턴스가 포함될 수 있습니다 MyClass. a와 함께 사용할 수있는 다형성 구문은 없습니다. trait왜냐하면 a trait는 본질적으로 프로그래머의 편의를 위해이를 사용하는 각 클래스에 복사되는 코드 이기 때문 입니다.

상위:

매뉴얼에 설명 된대로 :

기본 클래스에서 상속 된 멤버는 Trait에 의해 삽입 된 멤버로 대체됩니다. 우선 순위는 현재 클래스의 멤버가 Trait 메소드를 대체하며,이 메소드는 상속 된 메소드를 대체합니다.

따라서 다음 시나리오를 고려하십시오.

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

위의 MyClass 인스턴스를 만들 때 다음이 발생합니다.

  1. Interface IBase호출하려면 매개 변수없는 함수가 필요합니다 SomeMethod().
  2. 기본 클래스 BaseClass는이 메소드의 구현을 제공하여 필요를 충족시킵니다.
  3. trait myTrait라는 매개 변수가 기능을 제공 SomeMethod()뿐만 아니라, 우선합니다 오버 BaseClass-version을
  4. class MyClass자체 버전 제공 SomeMethod()- 우선합니다 오버 trait-version을.

결론

  1. Interface잠시 방법 본체의 기본 구현을 제공 할 수 없습니다 trait캔.
  2. InterfaceA는 다형성 , 상속 구조 - A는 동안 trait아닙니다.
  3. 여러 Interface들 같은 클래스에서 사용하고있는 여러 그렇게 할 수 trait의.

4
"특성은 추상 클래스의 C # 개념과 유사합니다."아니요, 추상 클래스는 추상 클래스입니다. 이 개념은 PHP와 C # 모두에 존재합니다. 하나의 유형 만 확장하는 확장 메소드와 달리 거의 모든 유형에서 특성을 사용할 수 있으므로 유형 기반 제한을 제거한 C #의 확장 메소드로 작성된 정적 클래스와 PHP의 특성을 비교합니다.
BoltClock

1
아주 좋은 논평-그리고 나는 당신에 동의합니다. 다시 읽을 때, 그것은 더 나은 비유입니다. 그래도 그것을 그것을 생각하는 것이 더 낫다고 믿습니다. mixin내 답변의 개회를 다시 방문했을 때 나는 이것을 반영하도록 업데이트되었습니다. 의견을 보내 주셔서 감사합니다. @BoltClock!
트로이 알 포드

1
C # 확장 메서드와 관련이 있다고 생각하지 않습니다. 확장 메소드는 단일 클래스 유형에 추가됩니다 (물론 클래스 계층에 따라) 그들의 목적은 여러 클래스에서 코드를 공유하고 혼란스럽게 만들지 않고 추가 기능으로 유형을 향상시키는 것입니다. 비교할 수 없습니다! 무언가를 재사용해야하는 경우, 일반적으로 공통 기능이 필요한 클래스와 관련된 별도의 클래스와 같이 자체 공간이 있어야 함을 의미합니다. 구현은 디자인에 따라 다를 수 있지만 대략 그 정도입니다. 특성은 코드를 잘못 작성하는 또 다른 방법입니다.
Sofija

클래스는 여러 인터페이스를 가질 수 있습니까? 그래프가 잘못되었는지 확실하지 않지만 클래스 X는 Y, Z를 구현합니다.
Yann Chabot

26

traits여러 클래스의 메소드로 사용할 수있는 메소드가 포함 된 클래스를 작성 하는 것이 유용 하다고 생각 합니다.

예를 들면 다음과 같습니다.

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

이 특성 을 사용 하는 모든 클래스에서이 "오류"메소드를 사용하고 사용할 수 있습니다 .

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

사용 interfaces하면 메소드 서명 만 선언 할 수 있지만 함수 코드는 선언 할 수 없습니다. 또한 인터페이스를 사용하려면을 사용하여 계층 구조를 따라야합니다 implements. 이것은 특성의 경우가 아닙니다.

완전히 다릅니다!


나는 이것이 특성의 나쁜 예라고 생각합니다. 클래스를 정수로 (지능적으로) 캐스팅하는 것과 근본적으로 유사한 방법이 없기 때문에 인터페이스에 to_integer포함될 가능성이 높습니다 IntegerCast.
Matthew

5
"to_integer"를 잊어 버리십시오 – 그것은 단지 예시 일뿐입니다. 예입니다. "안녕하세요, 세계". "example.com"
J. Bruni

2
이 툴킷 특성은 독립형 유틸리티 클래스가 제공 할 수 없었던 이점은 무엇입니까? 대신에 use Toolkit당신이 수 $this->toolkit = new Toolkit();또는 I는 특성 자체의 일부 혜택을 놓치고?
Anthony

당신 Something의 컨테이너 어딘가에서 @Anthonyif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101

20

위의 초보자는 대답이 어려울 수 있습니다. 이해하는 가장 쉬운 방법입니다.

특성

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

따라서 sayHello전체 함수를 다시 만들지 않고 다른 클래스에서 함수를 사용하려면 특성을 사용할 수 있습니다.

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

멋지다!

특성뿐만 아니라 특성 (function, variables, const ..)에서 무엇이든 사용할 수 있습니다. 또한 여러 특성을 사용할 수 있습니다.use SayWorld,AnotherTraits;

상호 작용

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

인터페이스와 특성이 다른 방법입니다. 구현 된 클래스의 인터페이스에서 모든 것을 다시 만들어야합니다. 인터페이스에 구현이 없습니다. interface는 함수와 const 만 가질 수 있고 변수는 가질 수 없습니다.

이게 도움이 되길 바란다!


5

특성을 설명하기 위해 자주 사용되는 은유는 특성과 구현의 인터페이스입니다.

이것은 대부분의 상황에서 그것에 대해 생각하는 좋은 방법이지만, 둘 사이에는 많은 미묘한 차이가 있습니다.

처음에는 instanceof운영자가 특성에 대해 작업하지 않으므로 (즉, 특성이 실제 개체가 아닙니다) 클래스에 특정 특성이 있는지 확인하거나 관련이없는 두 클래스가 특성을 공유하는지 확인할 수 없습니다 ). 이것이 수평 코드 재사용을위한 구성이라는 의미입니다.

있습니다 당신이 특성을 클래스에서 사용하는 모든 목록을 얻을 수있게된다 이제 PHP 함수가 있지만, 특성 상속 수단은 당신이 안정적으로 재귀 검사를 할 어떤 점에서 클래스가 특정 형질을 가지고 있는지 확인해야합니다 (예를있다 PHP doco 페이지의 코드). 그러나 그렇습니다. instanceof만큼 간단하고 깨끗하지는 않지만 IMHO는 PHP를 더 좋게 만드는 기능입니다.

또한 추상 클래스는 여전히 클래스이므로 다중 상속 관련 코드 재사용 문제를 해결하지 못합니다. 하나의 클래스 (실제 또는 추상) 만 확장 할 수 있지만 여러 인터페이스를 구현할 수 있습니다.

특성과 인터페이스가 의사 다중 상속을 만들기 위해 함께 사용하는 것이 좋습니다. 예 :

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

이렇게하면 instanceof를 사용하여 특정 Door 객체가 Keyed인지 아닌지를 확인할 수 있으며, 일관된 메소드 세트 등을 얻을 수 있으며 모든 코드는 KeyedTrait를 사용하는 모든 클래스에서 한 곳에 있습니다.


이 답변의 마지막 부분은 물론 @rdlowrey가 그의 게시물의 "Traits"아래 마지막 세 단락에서 더 자세하게 말한 내용입니다. 방금 간단한 뼈대 코드 스 니펫이 설명하는 데 도움이 될 것이라고 느꼈습니다.
Jon Kloske

특성을 사용하는 가장 좋은 OO 방법은 가능한 인터페이스를 사용하는 것입니다. 그리고 여러 하위 클래스가 해당 인터페이스에 대해 동일한 종류의 코드를 구현하고 해당 코드를 (추상적 인) 수퍼 클래스로 옮길 수없는 경우-> 특성으로 구현할 수없는 경우
player-one


3

기본적으로 특성을 자동화 된 "복사-붙여 넣기"로 간주 할 수 있습니다.

특성을 사용하는 것은 실행 전에 수행 할 작업을 알 방법이 없기 때문에 위험합니다.

그러나 특성은 상속과 같은 제한이 없기 때문에 더 유연합니다.

특성은 클래스에 무언가를 검사하는 메소드 (예 : 다른 메소드 또는 속성의 존재)를 삽입하는 데 유용 할 수 있습니다. 그것에 관한 좋은 기사 (하지만 프랑스어로, 죄송합니다) .

프랑스어를 읽을 줄 아는 사람들을 위해 GNU / Linux Magazine HS 54에는이 주제에 관한 기사가 있습니다.


특성이 기본 구현 인터페이스와 어떻게 다른지 여전히 알 수 없음
denis631

@ denis631 특성을 코드 스 니펫으로, 인터페이스를 서명 계약으로 볼 수 있습니다. 그것이 도움이 될 수 있다면, 그것을 포함 할 수있는 비공식적 인 클래스로 볼 수 있습니다. 도움이되는지 알려주세요.
Benj

PHP 특성은 매크로로 볼 수 있으며 매크로는 컴파일 타임에 확장되고 해당 코드 스 니펫을 해당 키로 별칭 지정합니다. 그러나 녹 특성은 다르게 보입니다 (또는 내가 틀렸습니까). 그러나 둘 다 단어 특성을 가지고 있기 때문에 동일한 개념을 의미하는 동일한 것으로 가정합니다. 녹 특성 링크 : doc.rust-lang.org/rust-by-example/trait.html
denis631

2

영어를 알고 trait의미 가 무엇인지 알면 바로 그 이름입니다. 을 입력하여 기존 클래스에 첨부하는 클래스가없는 메소드 및 특성 팩입니다 use.

기본적으로 단일 변수와 비교할 수 있습니다. 클로저 함수는 use이러한 변수를 스코프 외부에서 가져 와서 내부에 값을 가질 수 있습니다. 그들은 강력하고 모든 것에 사용될 수 있습니다. 특성을 사용하는 경우에도 마찬가지입니다.


2

다른 답변은 인터페이스와 특성의 차이점을 설명하는 데 큰 도움이되었습니다. 유용한 실세계 예제, 특히 특성이 인스턴스 변수를 사용할 수 있음을 보여주는 예제에 중점을 둘 것입니다. 최소한의 상용구 코드로 클래스에 동작을 추가 할 수 있습니다.

다시 말하지만, 다른 사람들이 언급했듯이 특성은 인터페이스와 잘 쌍을 이루어 인터페이스가 동작 계약을 지정하고 특성이 구현을 이행 할 수 있도록합니다.

클래스에 이벤트 게시 / 구독 기능을 추가하는 것은 일부 코드 기반에서 일반적인 시나리오 일 수 있습니다. 3 가지 일반적인 솔루션이 있습니다.

  1. 이벤트 펍 / 서브 코드를 사용하여 기본 클래스를 정의한 다음 이벤트를 제공하려는 클래스는 기능을 얻기 위해이를 확장 할 수 있습니다.
  2. 이벤트 퍼브 / 서브 코드로 클래스를 정의한 다음, 이벤트를 제공하려는 다른 클래스는 컴포지션을 통해 클래스를 사용하여 작성된 메소드를 정의하여 작성된 오브젝트를 랩핑하고 메소드 호출을 프록 싱 할 수 있습니다.
  3. 이벤트 펍 / 서브 코드로 특성을 정의한 다음, 이벤트를 제공하려는 다른 클래스 use는 특성을 가져 오기 위해 특성을 가져올 수 있습니다.

각각 얼마나 잘 작동합니까?

# 1 잘 작동하지 않습니다. 이미 다른 것을 확장하고 있기 때문에 기본 클래스를 확장 할 수 없다는 것을 알기 전까지는 그렇게 할 것입니다. 상속을 사용하는 것이 얼마나 제한되는지 분명해야하기 때문에 이것의 예를 보여주지 않을 것입니다.

# 2 & # 3 둘 다 잘 작동합니다. 몇 가지 차이점을 강조하는 예를 보여 드리겠습니다.

먼저 두 예제간에 동일한 코드가 있습니다.

인터페이스

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

사용법을 보여주는 몇 가지 코드 :

$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}

자, 이제 Auction특성을 사용할 때 클래스 의 구현이 어떻게 다른지 보여 드리겠습니다 .

먼저 # 2 (컴포지션 사용)의 모양은 다음과 같습니다.

class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}

# 3 (특성)의 모양은 다음과 같습니다.

trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}

내부의 코드 는 특성이 메소드를 보호 된 것으로 선언한다는 점을 제외하고 클래스 내부의 코드 EventEmitterTrait와 정확히 동일 합니다. 따라서 살펴볼 유일한 차이점은 클래스 의 구현입니다 .EventEmittertriggerEvent()Auction

그리고 그 차이는 크다. 컴포지션을 사용할 때 훌륭한 솔루션을 얻게되므로 원하는 EventEmitter만큼 많은 클래스 를 재사용 할 수 있습니다. 그러나 주요 단점은 Observable인터페이스에 정의 된 각 메소드마다 해당 메소드에 인수를 전달하는 지루한 상용구 코드를 작성하고 구현해야하기 때문에 작성하고 유지해야하는 상용구 코드가 많다는 것입니다 . 우리는 EventEmitter객체를 구성했습니다 . 사용 이 예제의 특성은 우리가 그것을 피할 수 있도록 우리가 도와 상용구 코드를 줄이고, 유지 보수성을 향상 .

그러나 Auction클래스가 전체 Observable인터페이스 를 구현하기를 원하지 않는 경우가 있습니다. 아마도 하나 또는 두 개의 메소드 만 노출하거나 전혀 메소드 서명을 정의 할 수있는 메소드를 노출하지 않을 수도 있습니다. 이 경우 여전히 구성 방법을 선호 할 수 있습니다.

그러나 특히 인터페이스에 많은 메소드가있어 많은 상용구를 작성하는 경우 대부분의 시나리오에서 특성이 매우 중요합니다.

* 실제로 두 가지를 모두 수행 할 수 있습니다- EventEmitter구성 적으로 사용하려는 경우 클래스를 정의하고 EventEmitterTrait특성 EventEmitter내부의 클래스 구현을 사용하여 특성도 정의하십시오. :)


1

특성은 다중 상속 목적 및 코드 재사용성에 사용할 수있는 클래스와 동일합니다.

클래스 내에서 특성을 사용할 수 있으며 'use keyword'를 사용하여 동일한 클래스에서 여러 특성을 사용할 수 있습니다.

인터페이스는 특성과 동일한 코드 재사용 성을 위해 사용하고 있습니다.

인터페이스는 다중 인터페이스를 확장하여 다중 상속 문제를 해결할 수 있지만 인터페이스를 구현할 때 클래스 내에 모든 메소드를 작성해야합니다. 자세한 내용을 보려면 아래 링크를 클릭하십시오.

http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php



0

가장 큰 차이점은 인터페이스를 사용하면 해당 인터페이스를 구현하는 각 클래스 내에서 각 메소드의 실제 구현을 정의해야하므로 많은 클래스가 동일한 인터페이스를 구현하지만 동작이 다르지만 특성은 코드에 삽입되는 덩어리입니다 수업; 또 다른 중요한 차이점은 특성 메소드는 인스턴스 메소드 일 수도 있고 인터페이스 메소드와 달리 클래스 메소드 또는 정적 메소드 일 수 있다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.