2017-10-24 20 views
0

컴포지션을 통해 2 개의 오브젝트를 하나의 선택적 오브젝트와 결합하고 일반적이고 깨끗한 API를 유지하고자합니다. 예를 들면 다음과 같습니다.2 개의 클래스를 결합한 PHP 1은 선택 사항 일 수 있습니다.

class Composition implements Object1Interface, Object2Interface 
{ 
    private $object1; 

    private $object2; 

    public function __construct($object1, $object2 = null) 
    { 
     $this->$object1 = $object1; 
     $this->$object2 = $object2; 
    } 

    // This will always work since the object is mandatory. 
    public function getId() { 
     return $this->object1->getId(); 
    } 

    // This may fail because the object2 can be null. 
    public function getName() { 
     return $this->object2->getName(); 
    } 
} 

null 개체를 프록시 할 수 있기 때문에 꽤 빨리 실패합니다. 이것을 해결할 수있는 좋은 방법은 무엇입니까?

$object2을 작성해야하며 $object2을 채우기 위해 항상 모든 생성자 데이터를 준비하지 못한 경우 선택적 생성자에 대한 모든 인수를 만들어야합니다. 이것은 더 이상 필수 인수를 갖지 않을 것이기 때문에 커다란 노 - no처럼 보인다. 나는 이제 체인

$composition->getObject2()->getName() 

이 허용 될 수 있지만, 내가 가지고 깨끗한 API를 파괴하고 있습니다 :

옵션이 $object2을 반환하는 방법을 생성하는 것입니다하지만 지금과 같은 사용자에 의해 체인이 필요합니다 행동 양식.

문제를 해결하기 위해 여기에서 수행 할 수있는 것이 있습니까? 아니면 간단히 말하면 솔루션 솔루션의 체인을 따라 가야합니까?

+0

'$ object2'에 액세스 할 때 예상 동작이 제공되지 않았습니까? 또한 체인이 도움이되지 않는다면 코드의 다른 지점에서 실패 할 것입니다. – Yoshi

+0

'public function getName() {if ($ this-> object2)는 $ this-> object2-> getName();을 반환합니다. new \ Exception ('...')을 던지십시오.'' –

답변

1

나는 null-safe operator과 같은 것을 의미한다고 생각합니다.

Hack과 달리 PHP에는이 연산자 (관련 RFC)가 없으므로 explicitly check에 null 값이 필요합니다. 또한 Option Type과 같은 것을 사용할 수 있습니다.

class Composition implements Object1Interface, Object2Interface 
{ 
    private $object1; 

    // ... 

    public function __construct(Object1Interface $object1 = null, Object2Interface $object2 = null) 
    { 
     if ($object1 !== null) { 
      $this->object1 = $object1; 
     } else { 
      $this->object1 = new class implements Object1Interface 
      { 
       public function getId() 
       { 
        return null; 
       } 
      }; 
     } 

     // ... 
    } 

    public function getId() { 
     return $this->object1->getId(); // Always works. 
    } 
} 
1

당신은 두 가지 옵션이 있습니다 : 하나는 항상 객체가 그것에서 아무것도 반환하기 전에 설정되어 있는지 확인하거나 대신에 "더미"개체를 사용하여 적어도 당신은 동적으로 종속 객체의 "기본"버전을 만들 수 있습니다 사용자가 제공하지 않은 경우 사용자가 제공 한 것입니다.

모든 것은 주변에 경비원을 추가해야하므로 전자는 상당히 혼란 스러울 수 있으므로 개인적으로 후자를 선호합니다.

후자를 훨씬 간단하게 구현할 수있는 한 가지는 인터페이스를 사용하여 예상 개체의 사양을 지정하고 실제 개체가 제공되지 않는 경우 생성자가 더미 개체를 인스턴스화하도록하는 것입니다.

interface AThingThatDoesSomething 
{ 
    public function getValue() : integer 
} 

class RealClass implements AThingThatDoesSomething 
{ 
    public function getValue() : integer 
    { 
     return mt_rand(); 
    } 
} 

class DummyClass implements AThingThatDoesSomething 
{ 
    public function getValue() : integer 
    { 
     // You could return a dummy value here, or throw an exception, or whatever you deem to be the correct behaviour for the dummy class. This implementation just returns 0 for simplicity 
     return 0; 
    } 
} 

class ConsumingClass 
{ 
    private $requiredInstance = null; 
    private $optionalInstance = null; 

    public function __construct(AThingThatDoesSomething $requiredInstance, AThingThatDoesSomething $optionalInstance = null) 
    { 
     if (null === $optionalInstance) 
     { 
      $optionalInstance = new DummyClass(); 
     } 

     $this->requiredInstance = $requiredInstance; 
     $this->optionalInstance = $optionalInstance; 
    } 

    public function getRequiredVal() : integer 
    { 
     return $this->requiredInstance->getValue(); 
    } 

    // You don't need to worry if the user supplied an optional instance here because if they didn't then the dummy instance will have been instantiated instead 
    public function getOptionalVal() : integer 
    { 
     return $this->optionalInstance->getValue(); 
    } 
} 

이것은 인위적인 예처럼 보일 수도 있고, 물론 당신은 맞을뿐만 아니라 계약에 의해 패턴이라는 디자인의 장점 중 하나 보여줍니다. 객체가 특정 기준 (이 경우 인터페이스를 구현)을 충족 시킨다면 객체가 실제로 아무 것도하지 않더라도 그러한 기준을 충족하는 객체를 대체 할 수 있습니다.

실생활에서 나는 로깅이 필요한 클래스에 이것을 사용합니다. psr \ log 패키지를 사용하고 생성자에서 새 NullLogger를 설정합니다. 실제 로깅이 필요하다면 setLogger()를 사용하여 로거를 전달할 수 있습니다. 그렇지만 설정하지 않으면 $ this-> logger에 대해 걱정할 필요가 없습니다.