[oop] 의존성 주입은 캡슐화를 희생해야합니까?

올바르게 이해하면 Dependency Injection의 일반적인 메커니즘은 클래스 생성자를 통해 또는 클래스의 공용 속성 (구성원)을 통해 주입하는 것입니다.

이것은 주입되는 의존성을 드러내고 캡슐화의 OOP 원칙을 위반합니다.

이 트레이드 오프를 식별하는 것이 정확합니까? 이 문제를 어떻게 처리합니까?

아래의 내 질문에 대한 답변도 참조하십시오.



답변

이 문제를 살펴 보는 또 다른 방법이 있습니다.

IoC / 종속성 주입을 사용할 때는 OOP 개념을 사용하지 않습니다. 분명히 우리는 OO 언어를 ‘호스트’로 사용하고 있지만 IoC의 아이디어는 OO가 아닌 구성 요소 지향 소프트웨어 엔지니어링에서 비롯된 것입니다.

구성 요소 소프트웨어는 종속성 관리에 관한 것입니다. 일반적으로 사용되는 예는 .NET의 어셈블리 메커니즘입니다. 각 어셈블리는 참조하는 어셈블리 목록을 게시하므로 실행중인 응용 프로그램에 필요한 부분을 훨씬 더 쉽게 결합하고 검증 할 수 있습니다.

IoC를 통해 OO 프로그램에 유사한 기술을 적용함으로써 프로그램을보다 쉽게 ​​구성하고 유지 관리 할 수 ​​있습니다. 의존성 (생성자 매개 변수 등)을 게시하는 것이 이것의 핵심 부분입니다. 컴포넌트 / 서비스 지향 세계에서와 같이 캡슐화는 실제로 적용되지 않습니다. 세부 정보 유출을위한 ‘구현 유형’은 없습니다.

불행히도 우리의 언어는 현재 세밀하고 객체 지향적 인 개념을 거친 구성 요소 지향 개념과 분리하지 않으므로이 점을 염두에 두어야합니다. 🙂


답변

그것은 좋은 질문 -하지만 어떤 점에서, 가장 순수한 형태의 캡슐 요구 객체가 의존성이 성취 한 그 어느 경우에 위반 될 수 있습니다. 종속성의 일부 제공자는 있어야 해당 개체가를 필요로 둘 것을 알고 Foo, 그리고 공급자가 제공하는 방법이하는 Foo개체에 있습니다.

일반적 으로이 후자의 경우는 생성자 인수 또는 setter 메소드를 통해 처리됩니다. 그러나 이것이 반드시 사실은 아닙니다. 예를 들어 Java의 최신 버전의 Spring DI 프레임 워크는 개인 필드에 주석을 달도록하고 (예 :로 @Autowired) 종속성을 공개하지 않고도 리플렉션을 통해 종속성을 설정합니다. 공개 메소드 / 생성자 클래스 이것은 당신이 찾고있는 일종의 해결책 일 수 있습니다.

즉, 생성자 주입이 큰 문제라고 생각하지 않습니다. 나는 항상 생성 후에 객체가 완전히 유효해야한다고 느꼈다. 그래서 그들의 역할을 수행하기 위해 필요한 것 (즉, 유효한 상태에 있음)은 어쨌든 생성자를 통해 공급되어야한다. 공동 작업자가 작동 해야하는 객체가있는 경우 생성자 가이 요구 사항을 공개적으로 알리고 클래스의 새 인스턴스를 만들 때이 요구 사항을 충족시키는 것이 좋습니다.

이상적으로 객체를 다룰 때 인터페이스를 통해 객체와 상호 작용하고, 더 많은 작업을 수행할수록 (DI를 통해 종속성이 연결됨) 실제로 생성자를 직접 처리 할 필요가 없습니다. 이상적인 상황에서 코드는 클래스의 구체적인 인스턴스를 처리하지 않으며 심지어는 클래스를 작성하지도 않습니다. 따라서 IFoo생성자 FooImpl가 자신의 작업을 수행해야한다는 것을 걱정 하지 않고 실제로 FooImpl존재 여부를 알지 못하고 DI를 통해 전달 됩니다. 이 관점에서 캡슐화는 완벽합니다.

이것은 물론 의견이지만, 제 생각에는 DI가 반드시 캡슐화를 위반하지는 않으며 실제로 내부에 필요한 모든 지식을 한 곳에 집중시켜 도움을 줄 수 있습니다. 이것은 그 자체로 좋은 일 일뿐 만 아니라 더 좋은 곳은 자신의 코드베이스 외부에 있으므로 작성하는 코드 중 어느 것도 클래스의 종속성에 대해 알 필요가 없습니다.


답변

이것은 주입되는 의존성을 드러내고 캡슐화의 OOP 원칙을 위반합니다.

솔직히 말하면 모든 것이 캡슐화를 위반합니다. 🙂 일종의 부드러운 원칙으로 잘 다루어야합니다.

그렇다면 캡슐화를 위반하는 것은 무엇입니까?

상속 은 않습니다 .

상속은 서브 클래스가 부모 구현의 세부 사항에 서브 클래스를 노출시키기 때문에 종종 ‘상속이 캡슐화를 깨뜨린 다’고 말합니다. (Gang of Four 1995 : 19)

측면 지향 프로그래밍 . 예를 들어 onMethodCall () 콜백을 등록하면 이상한 부작용 등을 추가하여 정상적인 메소드 평가에 코드를 삽입 할 수 있습니다.

C ++에서 친구 선언은 않습니다 .

루비의 클래스 확장은 않습니다 . 문자열 클래스가 완전히 정의 된 후 어딘가에 문자열 메소드를 재정의하십시오.

음, 물건을 많이는 않습니다 .

캡슐화는 좋고 중요한 원칙입니다. 그러나 유일한 것은 아닙니다.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}


답변

예, DI는 캡슐화 ( “정보 숨기기”라고도 함)를 위반합니다.

그러나 실제 문제는 개발자가 KISS (짧고 간결하게 유지) 및 YAGNI (You Ai n’t Gonna Need It) 원칙을 위반하는 변명으로 사용하는 경우에 발생합니다.

개인적으로 저는 간단하고 효과적인 솔루션을 선호합니다. 나는 주로 “new”연산자를 사용하여 필요할 때마다 언제 어디서나 상태 저장 종속성을 인스턴스화합니다. 간단하고 캡슐화되어 있고 이해하기 쉽고 테스트하기 쉽습니다. 그래서 왜 안돼?


답변

좋은 depenancy injection container / system은 생성자 주입을 허용합니다. 종속 개체는 캡슐화되며 공개적으로 노출 될 필요가 없습니다. 또한 DP 시스템을 사용하면 코드를 작성하는 개체를 포함하여 개체의 구성 방법에 대한 세부 정보를 “알지”않습니다. 거의 모든 코드가 캡슐화 된 객체에 대한 지식으로부터 보호 될뿐만 아니라 객체 구성에도 참여하지 않기 때문에이 경우 더 많은 캡슐화가 있습니다.

이제 생성 된 객체가 생성자에서 캡슐화 된 객체를 만드는 경우와 비교한다고 가정합니다. DP에 대한 나의 이해는 우리가이 책임을 대상으로부터 멀리하고 다른 사람에게주고 싶다는 것입니다. 이를 위해 DP 컨테이너 인 “다른 사람”은 캡슐화를 “위반”하는 친밀한 지식을 가지고 있습니다. 이점은 지식을 대상에서 벗어나는 것입니다. 누군가 가지고 있어야합니다. 나머지 응용 프로그램은 그렇지 않습니다.

의존성 주입 컨테이너 / 시스템은 캡슐화를 위반하지만 코드는 그렇지 않습니다. 사실, 코드는 그 어느 때보 다 “캡슐화”되어 있습니다.


답변

Jeff Sternal이 질문에 대한 의견에서 지적했듯이 답변은 캡슐화 를 정의하는 방법에 전적으로 달려 있습니다 .

캡슐화가 의미하는 두 가지 주요 캠프가있는 것 같습니다.

  1. 객체와 관련된 모든 것은 객체에 대한 방법입니다. 그래서하는 File객체에 방법이있을 수 있습니다 Save, Print, Display, ModifyText, 등
  2. 물체는 그 자체의 작은 세계이며 외부 행동에 의존하지 않습니다.

이 두 정의는 서로 모순됩니다. File개체 자체가 인쇄 될 수 있으면 프린터 동작에 크게 의존합니다. 다른 한편으로, 인쇄 할 수있는 것 ( 또는 그러한 인터페이스)에 대해서만 알고 있다면 IFilePrinter, File객체는 인쇄에 대해 아무것도 알 필요가 없으므로 작업을하면 객체에 대한 의존성이 줄어 듭니다.

따라서 첫 번째 정의를 사용하면 종속성 주입이 캡슐화를 중단합니다. 그러나 솔직히 나는 첫 번째 정의를 좋아하는지 모르겠습니다. 확실히 확장되지 않습니다 (그렇다면 MS Word는 하나의 큰 클래스 일 것입니다).

반면 에 캡슐화의 두 번째 정의를 사용하는 경우 종속성 주입은 거의 필수 입니다.


답변

캡슐화를 위반하지 않습니다. 공동 작업자를 제공하고 있지만 수업에서 사용 방법을 결정합니다. 당신이 따르는 한 Tell은 묻지 말고 괜찮습니다. 나는 생성자 주입이 바람직하다고 생각하지만, 세터는 똑똑하고 좋은 한 괜찮을 수 있습니다. 즉, 클래스가 나타내는 불변량을 유지하는 논리가 포함되어 있습니다.