[design-patterns] 의존성 주입 및 싱글 톤 디자인 패턴

의존성 주입 또는 싱글 톤 패턴을 사용할 때를 어떻게 식별합니까? 많은 웹 사이트에서 “Use Dependency injection over singleton pattern”을 읽었습니다. 그러나 나는 그들에게 전적으로 동의하는지 확실하지 않습니다. 내 중소 규모 프로젝트의 경우 싱글 톤 패턴 사용이 간단하다는 것을 확실히 알 수 있습니다.

예를 들어 로거. 사용할 수 Logger.GetInstance().Log(...)
있지만, 대신 로거의 인스턴스를 사용하여 생성하는 모든 클래스를 주입해야하는 이유는 무엇입니까?



답변

테스트에 기록 된 내용을 확인하려면 종속성 주입이 필요합니다. 또한 로거는 단일 항목이 아닙니다. 일반적으로 각 클래스마다 로거가 있습니다.

테스트 가능성을위한 객체 지향 디자인에 대한 이 프레젠테이션시청하면 싱글 톤이 왜 나쁜지 알 수 있습니다.

싱글 톤의 문제점은 특히 테스트에서 예측하기 어려운 전역 상태를 나타낸다는 것입니다.

객체는 사실상 싱글 톤이 될 수 있지만 여전히를 통하지 않고 의존성 주입을 통해 얻을 수 있습니다 Singleton.getInstance().

저는 Misko Hevery의 프레젠테이션에서 몇 가지 중요한 점을 나열하고 있습니다. 그것을 본 후에는 객체 가 종속성이 무엇인지 정의하는 것이 더 좋은 이유에 대한 완전한 관점을 얻을 수 있지만 생성 방법은 정의하지 않습니다 .


답변

싱글 톤은 공산주의와 같습니다. 둘 다 종이에서는 훌륭하게 들리지만 실제로는 문제로 폭발합니다.

싱글 톤 패턴은 객체 액세스의 용이성에 불균형 적으로 강조합니다. 모든 소비자가 AppDomain 범위 개체를 사용하도록 요구하여 컨텍스트를 완전히 피하고 다양한 구현에 대한 옵션을 남기지 않습니다. 수업에 인프라 지식을 포함 GetInstance()하는 동시에 표현력을 정확히 0으로 추가합니다 . 모든 클래스에 대해 변경하지 않고 한 클래스에서 사용하는 구현을 변경할 수 없기 때문에 실제로 표현력이 줄어 듭니다 . 일회성 기능을 추가 할 수는 없습니다.

클래스 때 또한, Foo의존 Logger.GetInstance(), Foo효과적으로 소비자들로부터 종속성을 숨어있다. 즉 Foo, 소스를 읽고에 의존한다는 사실을 밝히지 않으면 완전히 이해 하거나 자신있게 사용할 수 없습니다 Logger. 소스가 없으면 의존하는 코드를 얼마나 잘 이해하고 효과적으로 사용할 수 있는지 제한됩니다.

정적 속성 / 방법으로 구현 된 싱글 톤 패턴은 인프라 구현에 대한 해킹에 지나지 않습니다. 그것은 대안에 비해 눈에 띄는 이점을 제공하지 않으면 서 무수한 방법으로 당신을 제한합니다. 원하는대로 사용할 수 있지만 더 나은 디자인을 장려하는 실행 가능한 대안이 있으므로 권장되는 방법이되어서는 안됩니다.


답변

다른 사람들은 일반적으로 싱글 톤의 문제를 아주 잘 설명했습니다. Logger의 특정 사례에 대한 메모를 추가하고 싶습니다. 나는 정적 getInstance()또는 getRootLogger()방법을 통해 단일 항목으로 Logger (또는 정확하게 루트 로거)에 액세스하는 것이 일반적으로 문제가되지 않는다는 데 동의합니다 . (당신이 테스트하는 클래스에 의해 기록되는 것을보고 싶지 않다면-그러나 내 경험상 필자는 이것이 필요한 경우를 거의 기억할 수 없다. 그런 다음 다른 사람들에게는 이것이 더 시급한 문제가 될 수있다).

IMO는 일반적으로 테스트중인 클래스와 관련된 상태를 포함하지 않기 때문에 싱글 톤 로거는 걱정할 필요가 없습니다. 즉, 로거의 상태 (및 가능한 변경 사항)는 테스트 된 클래스의 상태에 전혀 영향을주지 않습니다. 따라서 단위 테스트가 더 이상 어렵지 않습니다.

대안은 생성자를 통해 (거의) 앱의 모든 단일 클래스에 로거를 삽입하는 것입니다. 대체 당신은 몇 가지 점에서 발견 할 때 것 – 인터페이스의 일관성을 위해, 문제의 클래스는 현재 아무것도 기록하지 않는 경우에도 주입해야한다 해주기 때문에,이 클래스에서 뭔가를 로그인해야합니다, 당신은 로거 필요를 DI에 대한 생성자 매개 변수를 추가하여 모든 클라이언트 코드를 깨야합니다. 저는이 두 가지 옵션을 모두 싫어하고, DI를 로깅에 사용하는 것은 구체적인 이점없이 이론적 인 규칙을 준수하기 위해 제 삶을 복잡하게 만들 것이라고 생각합니다.

따라서 내 결론은 (거의) 보편적으로 사용되지만 앱과 관련된 상태를 포함하지 않는 클래스는 안전하게 Singleton으로 구현할 수 있다는 것 입니다.


답변

대부분은 테스트에 관한 것이지만 완전하지는 않습니다. Singltons는 소비하기 쉽기 때문에 인기가 있었지만, singleton에는 여러 가지 단점이 있습니다.

  • 테스트하기 어렵습니다. 로거가 올바르게 작동하는지 확인하는 방법을 의미합니다.
  • 테스트하기 어렵습니다. 로거를 사용하는 코드를 테스트하고 있지만 테스트의 초점이 아닌 경우에도 테스트 환경이 로거를 지원하는지 확인해야합니다.
  • 때때로 당신은 싱글 톤을 원하지 않지만 더 많은 유연성을 원합니다.

DI를 사용하면 종속 클래스를 쉽게 사용할 수 있습니다. 생성자 args에 넣으면 시스템이이를 제공하는 동시에 테스트 및 구성 유연성을 제공합니다.


답변

Dependency Injection 대신 Singleton을 사용해야하는 유일한 경우는 Singleton이 List.Empty 등과 같은 불변 값을 나타내는 경우입니다 (불변 목록 가정).

Singleton에 대한 직감 검사는 “이것이 Singleton이 아닌 전역 변수라면 괜찮을까요?”여야합니다. 그렇지 않은 경우 Singleton 패턴을 사용하여 전역 변수를 작성하고 있으며 다른 접근 방식을 고려해야합니다.


답변

Monostate 기사를 확인하십시오. Singleton의 멋진 대안이지만 이상한 속성이 있습니다.

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

이런 종류의 무서운 것은 아닙니다. 매퍼는 실제로 save ()를 수행하기 위해 데이터베이스 연결에 의존하기 때문입니다. 그러나 다른 매퍼가 이전에 생성 된 경우이 단계를 건너 뛸 수 있습니다. 깔끔하면서도 지저분하지 않나요?


답변