의존성 주입 및 싱글 톤 디자인 패턴


89

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

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

답변:


66

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

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

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

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

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


89

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

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

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

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


9
@BryanWatts는 말했듯이 Singletons는 여전히 일반 중규모 응용 프로그램에서 훨씬 빠르고 오류가 적습니다. 보통 저는 (사용자 지정) 로거에 대해 둘 이상의 가능한 구현이 없으므로 **가 필요한 이유는 다음과 같습니다. 1. 인터페이스를 만들고 공용 멤버를 추가 / 변경해야 할 때마다 변경합니다. 2. 유지 관리 DI 구성 3. 전체 시스템에 이러한 유형의 단일 개체가 있다는 사실을 숨 깁니다. 4. 조기 우려 분리로 인한 엄격한 기능에 자신을 묶습니다.
Uri Abramson 2014 년

@UriAbramson : 당신이 선호하는 트레이드 오프에 대해 이미 결정을 내린 것 같으니, 나는 당신을 설득하려고하지 않을 것입니다.
Bryan Watts

@BryanWatts 아니고 경우, 아마도 내 댓글이었다 조금 너무 가혹하지만 actualy 않는 관심 내게 당신은 내가 자라 점 ...에 대해 무슨 말을들을 수있는 많은
열린 우리당 Abramson이

2
@UriAbramson : 충분합니다. 테스트 중 격리를 위해 구현을 교체하는 것이 중요하다는 데 적어도 동의하십니까?
Bryan Watts

6
싱글 톤과 의존성 주입의 사용은 상호 배타적이지 않습니다. 싱글 톤은 인터페이스를 구현할 수 있으므로 다른 클래스에 대한 종속성을 충족시키는 데 사용할 수 있습니다. 싱글 톤이라는 사실이 모든 소비자가 "GetInstance"메서드 / 속성을 통해 참조를 얻도록 강요하지는 않습니다.
Oliver

18

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

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

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

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


1
그럼에도 불구하고 싱글 톤은 고통 스러울 수 있습니다. 로거에 일부 추가 매개 변수가 필요한 경우 새 방법을 만들거나 (그리고 로거가 더 나빠지도록) 상관하지 않더라도 로거의 모든 소비자를 깨뜨릴 수 있습니다.
kyoryu

4
@kyoryu, IMHO가 (사실상) 표준 로깅 프레임 워크를 사용하는 것을 의미하는 "일반적인"경우에 대해 이야기하고 있습니다. (일반적으로 속성 / XML 파일 btw를 통해 구성 할 수 있습니다.) 물론 예외도 있습니다. 항상 그렇듯이. 이 점에서 내 앱이 예외적이라는 것을 안다면 실제로 Singleton을 사용하지 않을 것입니다. 그러나 "언젠가는 유용 할 수 있습니다"라는 과도한 엔지니어링은 거의 항상 낭비되는 노력입니다.
Péter Török

이미 DI를 사용하고 있다면 그다지 추가 엔지니어링이 아닙니다. (BTW, 나는 당신과 동의하지 않습니다, 나는 찬성입니다). 많은 로거는 "범주"정보 또는 일종의 일종의 정보를 필요로하며 추가 매개 변수를 추가하면 고통이 발생할 수 있습니다. 인터페이스 뒤에 숨기면 소비 코드를 깔끔하게 유지하고 다른 로깅 프레임 워크로 쉽게 전환 할 수 있습니다.
kyoryu

1
@kyoryu, 잘못된 가정에 대해 죄송합니다. 나는 반대표와 댓글을 보았 기 때문에 점을 연결했습니다. 잘못된 방식으로 밝혀졌습니다. :-( 다른 로깅 프레임 워크로 전환 할 필요성을 경험 한 적이 없지만 다시 합법적 일 수 있음을 이해합니다. 일부 프로젝트에 관심.
페테르 토록

5
내가 틀렸다면 저를 정정하십시오. 그러나 싱글 톤은 로거가 아닌 LogFactory를위한 것입니다. 또한 LogFactory는 apache commons 또는 Slf4j 로깅 파사드 일 가능성이 높습니다. 따라서 로깅 구현을 전환하는 것은 어쨌든 어렵지 않습니다. DI를 사용하여 LogFactory를 주입 할 때의 진정한 고통은 앱의 모든 인스턴스를 만들기 위해 지금 applicationContext로 이동해야한다는 사실이 아닙니까?
HDave

9

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

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

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


별로. 공유 된 변경 가능한 상태와 정적 종속성에 관한 것이므로 장기적으로 문제가 발생합니다. 테스트는 명백하고 자주 가장 고통스러운 예입니다.
kyoryu

2

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

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


1

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 ()를 수행하기 위해 데이터베이스 연결에 의존하기 때문입니다. 그러나 다른 매퍼가 이전에 생성 된 경우이 단계를 건너 뛸 수 있습니다. 깔끔하면서도 지저분하지 않나요?


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