객체를 생성자에 전달하거나 클래스에서 인스턴스화해야합니까?


10

다음 두 가지 예를 고려하십시오.

생성자에게 객체 전달

class ExampleA
{
  private $config;
  public function __construct($config)
  {
     $this->config = $config;
  }
}
$config = new Config;
$exampleA = new ExampleA($config);

클래스 인스턴스화

class ExampleB
{
  private $config;
  public function __construct()
  {
     $this->config = new Config;
  }
}
$exampleA = new ExampleA();

객체를 속성으로 추가하는 올바른 방법은 무엇입니까? 언제 다른 것을 사용해야합니까? 단위 테스트가 사용해야하는 것에 영향을 줍니까?



9
@FrustratedWithFormsDesigner-코드 검토에 적합하지 않습니다.
ChrisF

답변:


14

첫 번째 config객체는 다른 곳 에서 객체 를 생성하여 전달 하는 기능을 제공한다고 생각합니다 ExampleA. 의존성 주입이 필요한 경우 모든 인스턴스가 동일한 객체를 공유 할 수 있기 때문에 이것이 좋습니다.

반면에, 어쩌면 당신이 ExampleA 필요로 새로운 청소 config등의 각 인스턴스가 잠재적으로 다른 구성을 가질 수 경우와 두 번째 예제가 더 적합한 경우를,있을 수 있도록 개체를.


1
A는 단위 테스트에 좋고 B는 다른 상황에 좋습니다. 나는 종종 수동 DI를위한 생성자와 일반적인 사용법을위한 생성자를 두 개 가지고있다 (DI를 위해 Unity를 사용한다.
Ed James

3
@ EdWoodcock 그것은 실제로 다른 상황에 대한 단위 테스트에 관한 것이 아닙니다. 개체는 외부로부터의 종속성을 요구하거나 내부에서 관리합니다. 둘 다 또는 둘 다.
MattDavey 2012 년

9

테스트 가능성을 잊지 마십시오!

일반적으로 Example클래스 의 동작이 클래스 구성의 내부 상태를 저장 / 수정하지 않고 테스트 할 수있는 구성에 의존하는 경우 (예 : 속성 / Example수업의 회원 ).

따라서 나는 첫 번째 옵션으로 갈 것입니다 ExampleA


그것이 제가 테스트를 언급 한 이유입니다. 다음을 추가해 주셔서 감사합니다. :)
죄수

5

개체 의 종속성 수명관리 해야하는 책임이 있는 경우 생성자에서 개체를 만들고 소멸자에 배치하는 것이 좋습니다. *

객체가 종속성의 수명을 관리 할 책임이없는 경우 객체는 생성자로 전달되어 외부 (예 : IoC 컨테이너)에서 관리되어야합니다.

이 경우 ClassA가 $ config를 생성해야 할 책임이 있거나 구성이 ClassA의 각 인스턴스에 대해 고유하지 않은 한 $ config를 만드는 데 책임이 있다고 생각하지 않습니다.

* 테스트 가능성을 돕기 위해 생성자는 팩토리 클래스 / 메소드를 참조하여 생성자에 대한 종속성을 구성하여 응집력과 테스트 가능성을 높입니다.

//Object which manages the lifetime of its dependency (C#):
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA()
    {
        this.Config = new Config(); // Tightly coupled to Config class...
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

// Object which does not manage its dependency:
public class ClassA
{
    public Config Config { get; set; }

    public ClassA(Config config) // dependency may be injected...
    {
        this.Config = config;
    }
}

// Object which manages its dependency in a testable way:
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA(IConfigFactory configFactory) // dependency may be mocked...
    {
        this.Config = configFactory.BuildConfig();
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

2

최근에 건축 팀과 똑같은 토론을했으며 어떤 식 으로든 다른 방법으로 수행해야하는 미묘한 이유가 있습니다. 그것은 의존성 주입 (다른 사람들이 지적했듯이)과 생성자에서 새로 만든 객체의 생성을 실제로 제어 할 수 있는지 여부에 달려 있습니다.

예제에서 Config 클래스가 다음과 같은 경우 :

a) 풀 또는 팩토리 메소드에서 오는 것과 같이 사소한 할당이 없습니다.

b) 할당에 실패 할 수 있습니다. 생성자에 전달하면이 문제를 피할 수 있습니다.

c) 실제로 구성의 하위 클래스입니다.

객체를 생성자에 전달하면 유연성이 가장 높습니다.


1

아래 답변은 잘못되었지만 다른 사람들이 배울 수 있도록 계속하겠습니다 (아래 참조).

에서가 ExampleA, 같은 사용할 수있는 Config여러 클래스를 통해 인스턴스를. 그러나 Config전체 애플리케이션 내에 인스턴스 가 하나만 있어야 Config하는 경우 인스턴스가 여러 개인 것을 피 하려면 싱글 톤 패턴을 적용하는 것이 Config좋습니다. 그리고 ConfigSingleton 인 경우 대신 다음을 수행 할 수 있습니다.

class ExampleA
{
  private $config;
  public function __construct()
  {
     $this->config = Config->getInstance();
  }
}
$exampleA = new ExampleA();

에서는 ExampleB, 다른 한편으로는, 당신은 항상 별도의 인스턴스거야 Config의 각 인스턴스를 ExampleB.

적용해야하는 버전은 실제로 응용 프로그램이 다음 인스턴스를 처리하는 방법에 따라 다릅니다 Config.

  • 각 인스턴스가있는 경우 ExampleX별도의 인스턴스가 있어야합니다 Config, 함께 할 것입니다 ExampleB;
  • 의 각 인스턴스가 ExampleX하나의 인스턴스 만 공유 Config하는 경우 ExampleA with Config Singleton;
  • 의 인스턴스가있는 경우 ExampleX의 다른 인스턴스를 사용할 수 있습니다 Config, 스틱 ExampleA.

싱글 톤Config 으로 변환 하는 것이 왜 잘못 됩니까 ?

어제 싱글 톤 패턴 에 대해서만 배웠다는 사실을 인정해야합니다 ( 헤드 디자인 패턴의 첫 번째 책 읽기 ). 순진하게 나는이 예제에 대해 적용했지만 많은 사람들이 지적했듯이 한 가지 방법은 다른 방법입니다 (일부는 더 비밀스럽고 "당신이 잘못하고 있습니다!"), 이것은 좋은 생각이 아닙니다. 따라서 다른 사람들이 방금했던 것과 같은 실수를 저 지르지 못하게하기 위해 Singleton 패턴이 유해 할 수있는 이유 (댓글 및 인터넷 검색 결과에 따라)를 요약 합니다.

  1. 경우 ExampleA받는 사람 자체 참조 검색 Config인스턴스를, 클래스가 밀접하게 결합됩니다. ExampleA다른 버전 Config(예 : 일부 서브 클래스) 을 사용 하는 인스턴스가있는 방법은 없습니다 . ExampleAmock-up 인스턴스를 사용하여 테스트 Config할 방법이 없기 때문에 테스트하려는 경우 끔찍합니다 ExampleA.

  2. 거기의 전제는 인스턴스 하나와 하나가 될 것입니다 Config어쩌면 보유 지금 ,하지만 당신은 항상 확신 할 수없는 같은 뜻을 잡고 미래에 . 나중에 여러 인스턴스 Config가 바람직하다는 것이 밝혀지면 코드를 다시 작성하지 않고는 이것을 달성 할 수있는 방법이 없습니다.

  3. 유일무이 한 인스턴스 Config가 영원 토록 사실 일지라도 일부 하위 클래스 Config(여전히 하나의 인스턴스 만 있는)를 사용할 수 있기를 원할 수 있습니다 . 그러나 코드 는 메소드 인 getInstance()of 를 통해 인스턴스를 직접 가져 오기 때문에 서브 클래스를 가져올 방법이 없습니다. 다시 코드를 다시 작성해야합니다.Configstatic

  4. 사실 ExampleA용도는 Config단지의 API를 볼 때 적어도 숨겨집니다 ExampleA. 이것은 나쁜 것이거나 아닐 수도 있지만 개인적으로 나는 이것이 불리한 느낌이라고 생각합니다. 예를 들어, 유지 관리 할 때 Config다른 모든 클래스의 구현을 조사하지 않고 변경으로 인해 영향을받는 클래스를 찾는 간단한 방법은 없습니다 .

  5. SingletonExampleA사용 한다는 사실 자체가 문제가되지 않더라도 테스트 관점에서 여전히 문제가 될 수 있습니다. 싱글 톤 객체는 애플리케이션이 종료 될 때까지 지속되는 상태를 유지합니다. 하나의 테스트를 다른 테스트와 분리하기를 원할 때 (즉, 하나의 테스트를 실행해도 다른 테스트의 결과에 영향을 미치지 않아야 함) 단위 테스트를 실행할 때 문제가 될 수 있습니다. 이 문제를 해결하려면 각 테스트 실행 (전체 응용 프로그램을 다시 시작해야 함) 사이에 Singleton 개체를 삭제해야하며 시간이 오래 걸릴 수 있습니다 (지루하고 성가신 것은 말할 것도 없습니다). Config

이것을 말하면서, 나는 실제 응용 프로그램을 구현하지 않고 여기 에서이 실수를 저지른 것을 기쁘게 생각합니다 . 실제로 실제로 일부 클래스에 Singleton 패턴 을 사용하도록 최신 코드를 다시 작성하는 것을 고려 하고있었습니다. 변경 사항을 쉽게 되돌릴 수는 있지만 (물론 SVN에 모두 저장되어 있음) 여전히 시간을 낭비했을 것입니다.


4
나는 그렇게하지 않는 것이 좋습니다 .. 이런 식으로 당신 은 클래스를 단단히 결합 ExampleA하고 Config-이것은 좋지 않습니다.
Paul

@ 폴 : 맞습니다. 잘 잡았다, 그것에 대해 생각하지 않았다.
gablin

3
테스트 가능성 때문에 항상 싱글 톤 사용을 권장하지 않습니다. 그것들은 본질적으로 전역 변수이며 의존성을 조롱하는 것은 불가능합니다.
MattDavey 2012 년

4
나는 당신이 잘못한 이유로 싱글 톤을 다시 사용하는 것이 좋습니다.
Raynos 2012 년

1

가장 간단한 것은 부부이다 ExampleAConfig. 더 복잡한 것을해야하는 강력한 이유가 없다면 가장 간단한 방법을 사용해야합니다.

디커플링에 대한 이유 중 하나 ExampleAConfig의 테스트 용이성을 개선하는 것입니다 ExampleA. 직접 커플 링 은 느리거나 복잡하거나 빠르게 진화하는 방법이있는 ExampleA경우 의 테스트 성을 저하시킵니다 Config. 테스트의 경우 몇 마이크로 초 이상 실행되는 경우 속도가 느립니다. 의 모든 방법 Config이 간단하고 빠르면 간단한 접근 방식을 취하고에 직접 연결 ExampleA됩니다 Config.


1

첫 번째 예는 의존성 주입 패턴의 예입니다. 외부 의존성을 가진 클래스는 생성자, 설정자 등에 의해 의존성을 전달합니다.

이 접근법은 느슨하게 결합 된 코드를 생성합니다. 대부분의 사람들은 느슨한 결합이 좋은 것이라고 생각합니다. 객체의 한 특정 인스턴스를 다른 인스턴스와 다르게 구성 해야하는 경우 구성을 쉽게 대체 할 수 있기 때문에 테스트를 위해 모의 구성 객체를 전달할 수 있습니다. 의 위에.

두 번째 방법은 GRASP 제작자 패턴에 더 가깝습니다. 이 경우 개체는 자체 종속성을 만듭니다. 결과적으로 코드가 밀접하게 결합되므로 클래스의 유연성이 제한되고 테스트하기가 더 어려워집니다. 클래스의 한 인스턴스가 다른 클래스와 다른 종속성을 가지려면 서브 클래스를 선택해야합니다.

그러나 종속 오브젝트의 수명이 종속 오브젝트의 수명에 의해 결정되고 종속 오브젝트가 오브젝트 외부에서 사용되지 않는 경우에 적절한 패턴이 될 수 있습니다. 일반적으로 DI를 기본 위치로 설정하라고 조언하지만 그 결과를 알고있는 한 다른 접근 방식을 완전히 배제 할 필요는 없습니다.


0

클래스가 $config외부 클래스에 노출되지 않으면 생성자 내에 클래스를 만듭니다. 이런 식으로 내부 상태를 비공개로 유지합니다.

(가) 경우 $config적절하게 설정해야 할 자신의 내부 상태를 필요로 필요로 다음 몇 가지 외부 코드 (아마도 팩토리 클래스)에 초기화를 연기하는 것이 합리적 성공하고이를 주입, 사용하기 전에 (예를 들면, 초기화 된 데이터베이스 연결 또는 일부 내부 필드가 필요) 생성자 또는 다른 사람들이 지적했듯이 다른 객체와 공유 해야하는 경우.


0

예제 A는 Config 유형이 아니라 Config의 추상 수퍼 클래스 유형 인 경우 수신 된 객체 인 경우에 좋은 구체적 클래스 Config 분리 됩니다 .

ExampleB 강하게되는 콘크리트 클래스 구성으로 결합 나쁘다 .

객체를 인스턴스화하면 클래스간에 강력한 연결이 만들어집니다. 팩토리 클래스에서 수행해야합니다.

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