의존성 역전 원리는 무엇이며 왜 중요한가?
의존성 역전 원리는 무엇이며 왜 중요한가?
답변:
이 문서를 확인하십시오 : Dependency Inversion Principle .
기본적으로 말합니다 :
중요한 이유는 간단히 말해서 변경이 위험하고 구현이 아닌 개념에 따라 콜 사이트에서 변경의 필요성이 줄어든다는 것입니다.
효과적으로, DIP는 상이한 코드 조각들 사이의 결합을 감소시킨다. 아이디어는 로깅 기능을 구현하는 여러 가지 방법이 있지만이를 사용하는 방식은 시간이 비교적 안정적이어야한다는 것입니다. 로깅 개념을 나타내는 인터페이스를 추출 할 수있는 경우이 인터페이스는 구현보다 훨씬 안정적이어야하며 호출 사이트는 해당 로깅 메커니즘을 유지 관리하거나 확장하는 동안 수행 할 수있는 변경의 영향을 훨씬 덜받습니다.
또한 구현이 인터페이스에 종속되도록함으로써 런타임시 특정 환경에 더 적합한 구현을 선택할 수 있습니다. 경우에 따라 이것 또한 흥미로울 수 있습니다.
C #의 민첩한 소프트웨어 개발, 원칙, 패턴 및 사례와 민첩한 원칙, 패턴 및 사례는 Dependency Inversion Principle의 원래 목표와 동기를 완전히 이해하는 데 가장 적합한 리소스입니다. "The Dependency Inversion Principle"이라는 기사도 좋은 자료이지만, 이전에 언급 된 책으로 들어가는 초안의 요약 버전이라는 사실 때문에 이 원칙을보다 일반적인 조언과 구별하기위한 핵심 인 패키지 및 인터페이스 소유권은 Design Patterns (Gamma, et al.) 책에서 찾을 수있는 "구현이 아닌 인터페이스로의 프로그래밍"에 대한 조언입니다.
요약을 제공하기 위해 Dependency Inversion Principle은 주로 "상위 레벨"구성 요소가 "상위 레벨"구성 요소가 소유 한 인터페이스에 종속되도록 "상위 레벨"구성 요소에서 "하위 레벨"구성 요소로의 기존 종속성 방향을 반전시키는 것에 관한 것 입니다. . (참고 : 여기서 "높은 수준"구성 요소는 계층 구조 내에서 개념적 위치 일 필요는 없지만 외부 종속성 / 서비스가 필요한 구성 요소를 나타냅니다. 그렇게하면 이론적으로 구성 요소에서 이동 하는 것처럼 커플 링이 크게 줄어들지 는 않습니다. 이론적으로 더 가치가있는 구성 요소에는 덜 가치가 있습니다.
이것은 외부 의존성이 컴포넌트의 소비자에 의해 구현이 제공되어야하는 인터페이스의 관점에서 표현되는 컴포넌트를 설계함으로써 달성된다. 다시 말해, 정의 된 인터페이스는 컴포넌트 사용 방법이 아니라 컴포넌트에 필요한 것을 표현합니다 (예 : "IDoSomething"이 아닌 "INeedSomething").
Dependency Inversion Principle에서 언급하지 않는 것은 인터페이스를 사용하여 종속성을 추상화하는 간단한 방법입니다 (예 : MyService → [ILogger ⇐ Logger]). 이렇게하면 종속성의 특정 구현 세부 사항에서 구성 요소가 분리되지만 소비자와 종속성 간의 관계는 반전되지 않습니다 (예 : [MyService → IMyServiceLogger] ⇐ Logger).
Dependency Inversion Principle의 중요성은 기능의 일부 (로깅, 유효성 검사 등)에 외부 종속성에 의존하는 소프트웨어 구성 요소를 재사용 할 수 있다는 단일 목표로 나눌 수 있습니다.
이 일반적인 재사용 목표 내에서 두 가지 하위 유형의 재사용을 설명 할 수 있습니다.
하위 종속성 구현으로 여러 응용 프로그램 내에서 소프트웨어 구성 요소 사용 (예 : DI 컨테이너를 개발하고 로깅을 제공하려고하지만 컨테이너를 사용하는 모든 사람이 특정 로거에 컨테이너를 연결하지는 않음) 선택한 로깅 라이브러리를 사용하십시오).
진화하는 상황에서 소프트웨어 구성 요소 사용 (예 : 구현 세부 사항이 발전하는 여러 버전의 응용 프로그램에서 동일하게 유지되는 비즈니스 논리 구성 요소를 개발했습니다).
인프라 라이브러리와 같이 여러 응용 프로그램에서 구성 요소를 재사용하는 첫 번째 경우 목표는 이러한 종속성에 의존하기 위해서는 소비자가 자신의 라이브러리의 하위 종속성에 연결하지 않고 소비자에게 핵심 인프라를 제공하는 것입니다. 소비자들도 같은 의존성을 요구해야합니다. 라이브러리 소비자가 동일한 인프라 요구 사항 (예 : NLog와 log4net)에 대해 다른 라이브러리를 사용하기로 선택하거나 이전 버전과 호환되지 않는 필수 라이브러리의 이후 버전을 사용하기로 선택한 경우 문제가 될 수 있습니다. 도서관에서 필요합니다.
비즈니스 논리 구성 요소 (예 : "상위 레벨 구성 요소")를 재사용하는 두 번째 경우의 목표는 응용 프로그램의 핵심 도메인 구현을 구현 세부 사항의 변경 요구 (예 : 지속성 라이브러리, 메시징 라이브러리 변경 / 업그레이드)에서 분리하는 것입니다. , 암호화 전략 등). 이상적으로 응용 프로그램의 구현 세부 사항을 변경해도 응용 프로그램의 비즈니스 논리를 캡슐화하는 구성 요소가 손상되지 않아야합니다.
참고 : 일부는이 두 번째 사례를 실제 재사용으로 설명하는 데 반대 할 수 있습니다. 단일 진화하는 응용 프로그램 내에서 사용되는 비즈니스 논리 구성 요소와 같은 구성 요소는 단일 용도 일뿐입니다. 그러나 여기서 아이디어는 응용 프로그램의 구현 세부 사항을 변경할 때마다 새로운 컨텍스트와 다른 사용 사례를 렌더링하지만 궁극적 목표는 격리와 이식성으로 구분 될 수 있다는 것입니다.
이 두 번째 사례에서 Dependency Inversion Principle을 따르는 것이 약간의 이점을 제공 할 수 있지만 Java 및 C #과 같은 현대 언어에 적용되는 가치는 크게 관련이없는 수준으로 줄어든다는 점에 유의해야합니다. 앞에서 설명한 것처럼 DIP는 구현 세부 정보를 별도의 패키지로 완전히 분리합니다. 그러나 진화하는 애플리케이션의 경우, 비즈니스 도메인 측면에서 정의 된 인터페이스를 사용하면 구현 세부 사항 구성 요소의 변경 요구로 인해 더 높은 레벨의 구성 요소를 수정해야 할 필요가 없습니다. . 이 원칙의이 부분은 새로운 언어와 관련이없는 원칙이 체계화 될 때 (즉, C ++) 해당 언어와 관련된 측면을 반영합니다. 그것은 말했다
인터페이스의 간단한 사용, 종속성 주입 및 분리 된 인터페이스 패턴과 관련된이 원칙에 대한 자세한 설명은 여기를 참조하십시오 . 또한이 원칙이 JavaScript와 같은 동적 유형 언어와 어떤 관련이 있는지에 대한 설명은 여기를 참조하십시오 .
소프트웨어 응용 프로그램을 설계 할 때는 기본 및 기본 작업 (디스크 액세스, 네트워크 프로토콜 등)을 구현하는 하위 수준 클래스와 복잡한 논리 (비즈니스 흐름 등)를 캡슐화하는 클래스를 고려할 수 있습니다.
마지막 클래스는 저급 클래스에 의존합니다. 이러한 구조를 구현하는 자연스러운 방법은 저수준 클래스를 작성하고 일단 복잡한 클래스를 작성하는 것입니다. 높은 수준의 클래스는 다른 클래스로 정의되므로 논리적 인 방법입니다. 그러나 이것은 유연한 디자인이 아닙니다. 저수준 클래스를 교체해야하는 경우 어떻게됩니까?
종속성 반전 원리는 다음과 같습니다.
이 원칙은 소프트웨어의 높은 수준의 모듈이 낮은 수준의 모듈에 의존해야한다는 기존의 개념을 "반전"시키려고합니다. 여기서 상위 레벨 모듈은 하위 레벨 모듈에 의해 구현되는 추상화 (예 : 인터페이스의 방법 결정)를 소유합니다. 따라서 하위 모듈을 상위 모듈에 의존하게합니다.
종속성 반전이 잘 적용되면 애플리케이션의 전체 아키텍처 수준에서 유연성과 안정성이 제공됩니다. 이를 통해 애플리케이션이보다 안전하고 안정적으로 발전 할 수 있습니다.
일반적으로 계층화 된 아키텍처 UI는 비즈니스 계층에 의존하고 이는 데이터 액세스 계층에 의존합니다.
계층, 패키지 또는 라이브러리를 이해해야합니다. 코드가 어떻게 작동하는지 봅시다.
데이터 액세스 계층을위한 라이브러리 또는 패키지가 있습니다.
// DataAccessLayer.dll
public class ProductDAO {
}
그리고 데이터 액세스 계층에 의존하는 다른 라이브러리 또는 패키지 계층 비즈니스 로직.
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private ProductDAO productDAO;
}
종속성 반전은 다음을 나타냅니다.
고수준 모듈은 저수준 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.
추상화는 세부 사항에 의존해서는 안됩니다. 세부 사항은 추상화에 따라 달라집니다.
고수준 모듈과 저수준은 무엇입니까? 라이브러리 또는 패키지와 같은 모듈, 상위 수준 모듈은 전통적으로 의존성이 있고 의존도가 낮은 수준입니다.
다시 말해, 모듈 상위 레벨은 조치가 호출되는 위치이고 하위 레벨은 조치가 수행되는 위치입니다.
이 원칙을 근거로 합리적인 결론은 결단 사이에 의존성이 없어야하지만 추상화에 의존해야한다는 것입니다. 그러나 우리가 취하는 접근법에 따르면 투자 의존도를 잘못 적용 할 수 있지만 추상화입니다.
다음과 같이 코드를 수정했다고 상상해보십시오.
추상화를 정의하는 데이터 액세스 계층을위한 라이브러리 또는 패키지가 있습니다.
// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{
}
그리고 데이터 액세스 계층에 의존하는 다른 라이브러리 또는 패키지 계층 비즈니스 로직.
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private IProductDAO productDAO;
}
우리는 비즈니스와 데이터 액세스 간의 추상화 종속성에 의존하지만 여전히 동일합니다.
종속성 반전을 얻으려면 지속성 인터페이스가이 높은 수준의 논리 또는 도메인이 있고 낮은 수준의 모듈이 아닌 모듈 또는 패키지에서 정의되어야합니다.
먼저 도메인 계층이 무엇인지 정의하고 통신의 추상화는 지속성으로 정의됩니다.
// Domain.dll
public interface IProductRepository;
using DataAccessLayer;
public class ProductBO {
private IProductRepository productRepository;
}
지속성 계층이 도메인에 의존 한 후 종속성이 정의되면 지금 반전시킵니다.
// Persistence.dll
public class ProductDAO : IProductRepository{
}
(출처 : xurxodev.com )
개념을 잘 동화시키고 목적과 이점을 심화시키는 것이 중요합니다. 기계적으로 머물면서 전형적인 사례 저장소를 배우면 의존성 원칙을 적용 할 수있는 곳을 식별 할 수 없습니다.
그러나 왜 의존성을 뒤집는가? 구체적인 예를 넘어서는 주요 목표는 무엇입니까?
일반적으로 덜 안정적인 것들에 의존하지 않는 가장 안정적인 것들이 더 자주 변경 될 수 있습니다.
데이터베이스 또는 기술이 지속성과 통신하기 위해 설계된 도메인 논리 나 작업과 동일한 데이터베이스에 액세스하는 지속성 유형을 변경하는 것이 더 쉽습니다. 이 때문에이 변경이 발생하면 지속성을 변경하기가 쉽기 때문에 종속성이 반대로됩니다. 이런 식으로 도메인을 변경할 필요가 없습니다. 도메인 계층은 무엇보다 가장 안정적이므로 어떤 것에 의존해서는 안됩니다.
그러나이 저장소 예제 만있는 것은 아닙니다. 이 원칙이 적용되는 많은 시나리오가 있으며이 원칙을 기반으로하는 아키텍처가 있습니다.
종속성 반전이 정의의 핵심 인 아키텍처가 있습니다. 모든 도메인에서 가장 중요하며 도메인과 나머지 패키지 또는 라이브러리 간의 통신 프로토콜이 정의되었음을 나타내는 추상화입니다.
에서 청소 아키텍처 도메인은 중앙에 위치하고 의존성을 나타내는 화살표 방향으로 보면, 가장 중요하고 안정적인 층이 무엇인지 분명합니다. 외부 레이어는 불안정한 도구로 간주되므로 의존하지 마십시오.
(출처 : 8thlight.com )
도메인이 중앙 부분에 위치하고 포트가 도미노 외부에서 통신의 추상화 인 6 각형 아키텍처와 동일한 방식으로 발생합니다. 여기서도 도메인이 가장 안정적이며 전통적인 의존성이 반전된다는 것이 분명합니다.
나에게, 공식 기사에 설명 된 종속성 반전 원리 은 본질적으로 재사용 성이 떨어지는 모듈의 재사용 가능성을 높이고 C ++ 언어의 문제를 해결하는 방법으로 잘못 안내 된 시도입니다.
C ++의 문제는 헤더 파일에 일반적으로 개인 필드 및 메서드 선언이 포함되어 있다는 것입니다. 따라서 상위 레벨 C ++ 모듈에 하위 레벨 모듈의 헤더 파일이 포함 된 경우 해당 모듈의 실제 구현 세부 사항에 따라 다릅니다 . 그리고 그것은 분명히 좋은 것이 아닙니다. 그러나 이것은 오늘날 일반적으로 사용되는보다 현대적인 언어에서는 문제가되지 않습니다.
높은 수준의 모듈은 기본적으로 낮은 수준의 모듈보다 재사용 성이 떨어집니다. 전자는 일반적으로 후자보다 더 많은 응용 프로그램 / 컨텍스트입니다. 예를 들어, UI 화면을 구현하는 구성 요소는 응용 프로그램에 따라 최상위 수준이며 매우 완전합니다. 다른 응용 프로그램에서 이러한 구성 요소를 재사용하려고하면 생산성이 떨어지고 과도한 엔지니어링 만 가능합니다.
따라서 컴포넌트 B에 의존하는 (A에 의존하지 않는) 컴포넌트 A의 동일한 레벨에서 별도의 추상화를 생성하는 것은 컴포넌트 A가 실제로 다른 애플리케이션이나 컨텍스트에서 재사용하는 데 유용 할 경우에만 수행 할 수 있습니다. 그렇지 않다면 DIP를 적용하는 것이 좋지 않습니다.
기본적으로 그것은 말합니다 :
클래스는 특정 세부 사항 (구현)이 아닌 추상화 (예 : 인터페이스, 추상 클래스)에 의존해야합니다.
좋은 답변과 좋은 예는 이미 다른 사람들이 제공 한 것입니다.
DIP 이유 가 중요한 는 OO 원칙 "느슨하게 결합 된 디자인"을 보장하기 때문입니다.
소프트웨어의 개체는 하위 개체에 따라 일부 개체가 최상위 개체 인 계층으로 들어가서는 안됩니다. 낮은 수준의 개체를 변경하면 최상위 개체로 리플 스루되어 소프트웨어가 변경되기 매우 취약합니다.
'최상위'개체가 매우 안정적이며 변경에 취약하지 않기를 원하므로 종속성을 반전시켜야합니다.
Dependency Inversion Principle을 명시하는 훨씬 명확한 방법은 다음과 같습니다.
복잡한 비즈니스 로직을 캡슐화하는 모듈은 비즈니스 로직을 캡슐화하는 다른 모듈에 직접 의존해서는 안됩니다. 대신, 단순한 데이터에 대한 인터페이스에만 의존해야합니다.
즉, Logic
사람들이 일반적으로하는 것처럼 수업을 구현하는 대신 :
class Dependency { ... }
class Logic {
private Dependency dep;
int doSomething() {
// Business logic using dep here
}
}
당신은 다음과 같은 일을해야합니다 :
class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
private Dependency dep;
...
}
class Logic {
int doSomething(Data data) {
// compute something with data
}
}
Data
와 DataFromDependency
같은 모듈에 살아야 Logic
하지와 함께,Dependency
.
왜 이렇게합니까?
Dependency
변경 하면 변경할 필요가 없습니다Logic
.Logic
수행하는 작업을 이해하는 것이 훨씬 간단한 작업입니다. ADT처럼 보이는 작업에서만 작동합니다.Logic
이제 더 쉽게 테스트 할 수 있습니다. 이제 Data
가짜 데이터로 직접 인스턴스화 하여 전달할 수 있습니다. 모의 또는 복잡한 테스트 스캐 폴딩이 필요하지 않습니다.DataFromDependency
이는 직접 참조, Dependency
같이 동일한 모듈에 Logic
다음, Logic
모듈은 여전히 직접에 따라 Dependency
컴파일시에 모듈. 당 원칙의 삼촌 밥의 설명 , 그것을 피하는 것은 DIP의 전체 지점입니다. 오히려, DIP를 수행하기 위해, Data
동일 모듈에 있어야 Logic
하지만, DataFromDependency
같은 모듈에 있어야한다 Dependency
.
제어 역전IoC ( )는 개체가 프레임 워크에 의존성을 요구하지 않고 외부 프레임 워크가 의존성을 전달하는 디자인 패턴입니다.
기존 조회를 사용하는 의사 코드 예 :
class Service {
Database database;
init() {
database = FrameworkSingleton.getService("database");
}
}
IoC를 사용하는 비슷한 코드 :
class Service {
Database database;
init(database) {
this.database = database;
}
}
IoC의 이점은 다음과 같습니다.
의존성 역전 지점은 재사용 가능한 소프트웨어를 만드는 것입니다.
아이디어는 서로 의존하는 두 개의 코드 대신 추상적 인 인터페이스에 의존한다는 것입니다. 그런 다음 다른 것없이 두 조각을 재사용 할 수 있습니다.
가장 일반적인 방법은 Spring in Java와 같은 IoC (Inversion of Control) 컨테이너를 사용하는 것입니다. 이 모델에서 객체의 속성은 객체가 나가고 종속성을 찾는 대신 XML 구성을 통해 설정됩니다.
이 의사 코드를 상상해보십시오 ...
public class MyClass
{
public Service myService = ServiceLocator.service;
}
MyClass는 Service 클래스와 ServiceLocator 클래스에 직접 의존합니다. 다른 응용 프로그램에서 사용하려면 두 가지가 모두 필요합니다. 이제 이것을 상상해보십시오 ...
public class MyClass
{
public IService myService;
}
이제 MyClass는 단일 인터페이스 인 IService 인터페이스를 사용합니다. 우리는 IoC 컨테이너가 실제로 그 변수의 값을 설정하도록했습니다.
이제 MyClass는 다른 두 클래스의 종속성을 가져 오지 않고도 다른 프로젝트에서 쉽게 재사용 할 수 있습니다.
더 좋은 점은 MyService의 종속성과 해당 종속성의 종속성을 끌 필요가 없다는 것입니다.
회사의 "고급"직원이 계획 실행에 대해 비용을 지불하고 이러한 계획이 많은 "저급"직원 계획의 총괄 실행에 의해 제공된다고 가정하면이를 말할 수 있습니다. 상위 직원의 계획 설명이 어떤 식 으로든 하위 직원의 특정 계획과 결합되는 경우 일반적으로 끔찍한 계획입니다.
고위 임원이 "배달 시간을 개선"할 계획이 있고 해운 회사 직원이 매일 아침 커피를 마시고 스트레칭을해야한다고 나타내는 경우, 해당 계획은 밀접하게 연계되어 있고 응집력이 낮습니다. 그러나 계획에서 특정 직원을 언급하지 않고 실제로 "작업을 수행 할 수있는 실체가 작동 할 준비가되어 있어야합니다"라는 계획이 필요한 경우 계획이 느슨하게 결합되어보다 응집력이 높아집니다. 계획이 겹치지 않고 쉽게 대체 될 수 있습니다. . 계약자 또는 로봇은 직원을 쉽게 대체 할 수 있으며 높은 수준의 계획은 변경되지 않습니다.
종속성 반전 원리에서 "높은 수준"은 "더 중요한"을 의미합니다.
위의 답변에 좋은 설명이 있음을 알 수 있습니다. 그러나 간단한 예제로 쉬운 설명을 제공하고 싶습니다.
Dependency Inversion Principle은 프로그래머가 하드 코딩 된 종속성을 제거하여 응용 프로그램이 느슨하게 연결되고 확장 될 수 있도록합니다.
이것을 달성하는 방법 : 추상화를 통해
의존성 역전없이 :
class Student {
private Address address;
public Student() {
this.address = new Address();
}
}
class Address{
private String perminentAddress;
private String currentAdrress;
public Address() {
}
}
위의 코드 스 니펫에서 주소 개체는 하드 코딩됩니다. 대신 의존성 역전을 사용하고 생성자 또는 setter 메소드를 통해 주소 객체를 주입 할 수 있다면. 보자
의존성 역전 :
class Student{
private Address address;
public Student(Address address) {
this.address = address;
}
//or
public void setAddress(Address address) {
this.address = address;
}
}
의존성 역전 : concretion이 아닌 추상화에 의존합니다.
제어 역전 : 메인 대 추상화, 메인이 시스템의 접착제 인 방법.
이것에 대해 좋은 소식이 있습니다.
https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/
https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/
https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/
하자 말 우리는 두 개의 클래스를 가지고 : Engineer
및Programmer
:
클래스 엔지니어는 아래와 같이 프로그래머 클래스에 의존합니다.
class Engineer () {
fun startWork(programmer: Programmer){
programmer.work()
}
}
class Programmer {
fun work(){
//TODO Do some work here!
}
}
이 예제에서 클래스 Engineer
는 클래스 에 의존합니다 Programmer
. 변경해야 할 경우 어떻게됩니까?Programmer
어떻게됩니까?
분명히 나도 변경해야합니다 Engineer
. (이 시점에서OCP
에서도 위반)
그렇다면이 엉망진창을 어떻게 정리해야합니까? 답은 실제로 추상화입니다. 추상화하여이 두 클래스 간의 종속성을 제거 할 수 있습니다. 예를 들어, Interface
Programmer 클래스를 만들 수 있으며 이제부터 사용하려는 모든 클래스는 Programmer 클래스 Programmer
를 사용해야합니다.Interface
때문에 추상화의, 그리고 프로그래머 클래스를 변경하여, 우리가 그것을 사용 된 모든 클래스를 변경하지 않아도 우리 익숙한.
참고 : DependencyInjection
우리가 할 데 도움이 될 수 있습니다 DIP
및 SRP
도.
일반적으로 좋은 답변을 더하기 위해 좋은 연습과 나쁜 연습을 보여주기 위해 작은 샘플을 추가하고 싶습니다. 그리고 네, 나는 돌을 던지는 사람이 아닙니다!
콘솔 I / O를 통해 문자열을 base64 형식으로 변환 하는 작은 프로그램이 필요합니다 . 순진한 접근 방식은 다음과 같습니다.
class Program
{
static void Main(string[] args)
{
/*
* BadEncoder: High-level class *contains* low-level I/O functionality.
* Hence, you'll have to fiddle with BadEncoder whenever you want to change
* the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
* problems with I/O shouldn't break the encoder!
*/
BadEncoder.Run();
}
}
public static class BadEncoder
{
public static void Run()
{
Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
}
}
DIP는 기본적으로 상위 레벨 컴포넌트는 하위 레벨 구현에 의존해서는 안된다고 말합니다. 여기서 "레벨"은 Robert C. Martin ( "Clean Architecture")에 따르면 I / O와의 거리입니다. 그러나이 곤경에서 어떻게 벗어날 수 있습니까? 중앙 인코더가 인터페이스 구현 방식을 방해하지 않고 인터페이스에만 의존하게함으로써 간단히 :
class Program
{
static void Main(string[] args)
{
/* Demo of the Dependency Inversion Principle (= "High-level functionality
* should not depend upon low-level implementations"):
* You can easily implement new I/O methods like
* ConsoleReader, ConsoleWriter without ever touching the high-level
* Encoder class!!!
*/
GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter()); }
}
public static class GoodEncoder
{
public static void Run(IReadable input, IWriteable output)
{
output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
}
}
public interface IReadable
{
string ReadInput();
}
public interface IWriteable
{
void WriteOutput(string txt);
}
public class ConsoleReader : IReadable
{
public string ReadInput()
{
return Console.ReadLine();
}
}
public class ConsoleWriter : IWriteable
{
public void WriteOutput(string txt)
{
Console.WriteLine(txt);
}
}
GoodEncoder
I / O 모드를 변경하기 위해 손대지 않아도됩니다. 클래스는 알고있는 I / O 인터페이스에 만족합니다. 의 낮은 수준의 구현 IReadable
과는 IWriteable
이제까지 그것을 귀찮게하지 않습니다.
GoodEncoder
두 번째 예 에 의존하지 않습니다 . DIP 예제를 만들려면 여기에서 추출한 인터페이스 "소유"에 대한 개념을 도입해야합니다. 특히 구현이 외부에있는 동안 GoodEncoder와 동일한 패키지에 배치해야합니다.
의존성 역전 원리 (DIP)는
i) 고수준 모듈은 저수준 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.
ii) 추상화는 세부 사항에 의존해서는 안됩니다. 세부 사항은 추상화에 의존해야합니다.
예:
public interface ICustomer
{
string GetCustomerNameById(int id);
}
public class Customer : ICustomer
{
//ctor
public Customer(){}
public string GetCustomerNameById(int id)
{
return "Dummy Customer Name";
}
}
public class CustomerFactory
{
public static ICustomer GetCustomerData()
{
return new Customer();
}
}
public class CustomerBLL
{
ICustomer _customer;
public CustomerBLL()
{
_customer = CustomerFactory.GetCustomerData();
}
public string GetCustomerNameById(int id)
{
return _customer.GetCustomerNameById(id);
}
}
public class Program
{
static void Main()
{
CustomerBLL customerBLL = new CustomerBLL();
int customerId = 25;
string customerName = customerBLL.GetCustomerNameById(customerId);
Console.WriteLine(customerName);
Console.ReadKey();
}
}
참고 : 클래스는 특정 세부 정보 (인터페이스 구현)가 아닌 인터페이스 또는 추상 클래스와 같은 추상화에 의존해야합니다.