로버트 마틴이 주장한 다섯 가지 설계 원칙
✓ SRP(단일 책임 원칙, Single Responsibility Principle)
✓ OCP(개방 폐쇄 원칙, Open Closed Principle)
✓ LSP(리스코프의 대입 원칙, Liskov Substitution Principle)
✓ ISP(인터페이스 분리 원칙, Interface Segregation Principle)
✓ DIP(의존성 역전 원칙, Dependency Inversion Principle)
=> Design Pattern의 기반을 이룸
<SRP 적용 전>
위의 그림을 보면, start(), stop(), getOil() 3개의 메소드에 의해 제공되는 기능들은 Car를 책임지고 있음(요구사항을 만족시키고 있음)
Car 클래스는 네개의 actor를 책임지고 있음(책임지는 기능들을 제공하는 메소드를 가지고 있음) 단 하나의 액터만을 책임져야한다는 "단일 책임 원칙"에 위배됨
<SRP 적용 후>
그림 출처 https://yoongrammer.tistory.com/96
일반화 관계를 적절하게 사용했는지를 점검하는 원칙
일반화 관계
A가 사용되는 것을 B의 인스턴스로 치환될 수 있다.
B is a A
B is a kind of A
LSP에서 일반화 관계는 슈퍼 클래스가 제공하는 오퍼레이션과 파생 클래스에서 제공하는 오퍼레이션 간에는 행위적으로 일관성이 있도록 설계가 되어야 한다는 원칙
프로그램에서 슈퍼 클래스의 인스턴스 대신에 파생 클래스의 인스턴스로 대체하여도 프로그램의 의미는 변화되지 않도록 설계
자식 클래스가 부모 클래스보다 더 많은 일을 할 수 있도록 재정의 가능(부모 클래스의 기능을 자식 클래스에서 오버라이딩 가능) => 부모 클래스를 확장시켰다고 볼 수 있음
<행위 일관성>
<ISP 적용 전>
public interface PrintingService {
public void print();
public void fax();
public void copy();
}
public class MultiPrinter implements PrintingService {
@Override
public void print() {
System.out.println("복합기 출력");
}
@Override
public void fax() {
System.out.println("복합기 팩스");
}
@Override
public void copy() {
System.out.println("복합기 복사");
}
public class NewPrinter implements PrintingService {
@Override
public void print() {
System.out.println("새 프린터 출력");
}
@Override
public void fax() {
throw new UnsupportedOperationException("Not Supported");
}
@Override
public void copy() {
throw new UnsupportedOperationException("Not Supported");
}
=> 새로운 인터페이스를 추가하려고 하는 경우, print() 기능만을 사용하고 싶은데 전혀 사용하지 않는 fax()나 copy()를 사용하도록 강제한다. 하지만 fax()나 copy() 연산에 복사할 페이지 수를 인자로 요구하도록 변경된다면 NewPrinter도 영향을 받는다. 하나의 큰 인터페이스(fat interface) 대신에 클라이언트가 필요로 하는 연산만을 가지고 있는 인터페이스를 제공하면 된다.
인터페이스 분리
-> ProfessionalPrintingService 인터페이스를 만들어 NewPrinter가 이 인터페이스를 통해 print() 기능을 제공할 수 있게 함
➕ MultiPrinter도 ProfessionalPrintingService 인터페이스를 구현하여 print() 서비스 제공할 수 있음
- 상위 모듈은 하위 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
- 추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.
<DIP 적용>
도메인 핵심 로직(High-level module)에서 직접적으로 Low-level model로 의존성 관계를 맺는 대신에 중간에 징검다리 역할이 필요 -> 변화가 일어나는 경우, 변화들을 포용할 수 있는 개념을 추상 클래스나 인터페이스로 모델링
그 후, 의존성 역전 -> 주변 환경으로부터 도메인 핵심 로직으로 의존성을 갖도록 의존 관계를 역전
<패키지 관점에서 본 DIP>