다른 답변은 인터페이스와 특성의 차이점을 설명하는 데 큰 도움이되었습니다. 유용한 실세계 예제, 특히 특성이 인스턴스 변수를 사용할 수 있음을 보여주는 예제에 중점을 둘 것입니다. 최소한의 상용구 코드로 클래스에 동작을 추가 할 수 있습니다.
다시 말하지만, 다른 사람들이 언급했듯이 특성은 인터페이스와 잘 쌍을 이루어 인터페이스가 동작 계약을 지정하고 특성이 구현을 이행 할 수 있도록합니다.
클래스에 이벤트 게시 / 구독 기능을 추가하는 것은 일부 코드 기반에서 일반적인 시나리오 일 수 있습니다. 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
와 정확히 동일 합니다. 따라서 살펴볼 유일한 차이점은 클래스 의 구현입니다 .EventEmitter
triggerEvent()
Auction
그리고 그 차이는 크다. 컴포지션을 사용할 때 훌륭한 솔루션을 얻게되므로 원하는 EventEmitter
만큼 많은 클래스 를 재사용 할 수 있습니다. 그러나 주요 단점은 Observable
인터페이스에 정의 된 각 메소드마다 해당 메소드에 인수를 전달하는 지루한 상용구 코드를 작성하고 구현해야하기 때문에 작성하고 유지해야하는 상용구 코드가 많다는 것입니다 . 우리는 EventEmitter
객체를 구성했습니다 . 사용 이 예제의 특성은 우리가 그것을 피할 수 있도록 우리가 도와 상용구 코드를 줄이고, 유지 보수성을 향상 .
그러나 Auction
클래스가 전체 Observable
인터페이스 를 구현하기를 원하지 않는 경우가 있습니다. 아마도 하나 또는 두 개의 메소드 만 노출하거나 전혀 메소드 서명을 정의 할 수있는 메소드를 노출하지 않을 수도 있습니다. 이 경우 여전히 구성 방법을 선호 할 수 있습니다.
그러나 특히 인터페이스에 많은 메소드가있어 많은 상용구를 작성하는 경우 대부분의 시나리오에서 특성이 매우 중요합니다.
* 실제로 두 가지를 모두 수행 할 수 있습니다- EventEmitter
구성 적으로 사용하려는 경우 클래스를 정의하고 EventEmitterTrait
특성 EventEmitter
내부의 클래스 구현을 사용하여 특성도 정의하십시오. :)