"어떤 클래스를 변경해야하는 이유는 오직 하나뿐이어야 한다" - 로버트 C.마틴
아래와 같이 남자라고 하는 클래스와 남자 클래스에 의존하는 다양한 클래스가 있다고 하면, 해당 남자는 여러 역할과 책임이 있다.
이러한 경우 단일 책임원칙에 위배되기 때문에 역할(책임)을 분리한다면 아래와 같이 여러 클래스로 나눌 수 있다.
class 강아지 {
final static Boolean 수컷 = true;
final static Boolean 암컷 = true;
Boolean 성별;
void 소변보다() {
if( this.성별 == 수컷 ) {
// 한쪽 다리를 들고 소변을 본다.
}
else {
// 뒷다리 두개를 굽혀 앉은 자세로 소변을 본다.
}
}
}
위의 예제 소스는 강아지가 수컷인지 암컷인지에 따라서 메서드 내부에서 분기 처리가 진행된다. 이는 단일 책임(행위) 원칙을 위배하고 있는 것이다. 메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 대표적인 예제가 바로 if 문이다. 이런 경우 단일 책임 원칙을 적용해 코드를 리팩토링하면 아래처럼 만들 수 있다.
abstract class 강아지 {
abstract void 소변보다();
}
class 수컷강아지 extends 강아지 {
void 소변보다() {
// 한쪽 다리를 들고 소변을 본다.
}
}
class 암컷강아지 extends 강아지 {
void 소변보다() {
// 뒷다리 두개를 굽혀 앉은 자세로 소변을 본다.
}
}
"소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야한다." - 로버트 C.마틴
위 문장을 조금 더 의역하면 "자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야한다."로 나타낼 수 있다.
위 그림을 보면 오라클을 MySQL 이나 MS-SQL로 교체해도 JDBC 인터페이스라는 완충 장치로 인해서 자바 애플리케이션은 변화에 닫혀있다.
그래서 데이터베이스를 교체한다는 것은 데이터베이스가 자신의 확장에는 열려 있다는 것이다.
"서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다." - 로버트 C.마틴
위 문장대로 구현된 프로그램이라면 이미 리스코프 치환 원칙을 지키고 있는 것.
하지만 위 문장대로 구현되지 않는 코드가 있을 수 있는데, 바로 상속이 조직도나 계층도 형태로 구축된 경우이다.
위의 예는 계층도/조직도인 경우인데 딸이 아버지, 할아버지 역할을 하는 것이 논리에 맞지 않음을 알 수 있다.
리스코프 치환 원칙을 완벽하게 지원하는 경우는 아래와 같은 동물 분류도와 같은 경우이다.
"클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다." - 로버트 C.마틴
단일 책임원칙의 예를 다시 보면 남자가 단일 책임 원칙으로 여러 클래스로 분리됨을 보였는데, 이 방법 말고 다른 방법인 ISP, 인터페이스 분할 원칙도 존재한다.
이런 식으로 어머니와 있을 때는 아들 인터페이스로 제한하고, 직장상사와 있을 때는 사원 인터페이스로 제한하는 것이 바로 인터페이스 분할 원칙의 핵심이다.
"고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다."
"추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야한다."
"자주 변경되는 구체(Concrete) 클래스에 의존하지 마라" - 로버트 C.마틴
자동차와 스노우타이어 사이에는 위와 같은 의존 관계가 있다. 자동차가 스노우타이어에 의존한다.
그런데 계절이 바뀌면 일반타이어로 교체해야하는데, 이런 경우, 자동차 자신보다 더 자주 변하는 스노우타이어에 의존하고 있기 때문에 아래와 같이 추상화된 타이어 인터페이스에만 의존하게 함으로써 자동차는 영향을 받지 않는 형태로 구성해야 한다.
위 처럼 의존 방향이 역전 된 것을 의존 역전 원칙이라고 한다.
자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다.
SRP(단일 책임 원칙) : 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
OCP(개방 폐쇄 원칙) : 자산의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야한다.
LSP(리스코프 치환 원칙) : 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야한다.
ISP(인터페이스 분리 원칙) : 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.
DIP(의존 역전 원칙) : 자신보다 변하기 쉬운 것에 의존하지 마라.