객체지향 설계 5원칙 - SOLID
- SRP(Single Responsiblity Principle): 단일 책임 원칙
- OCP(Open Closed Principle): 개방 폐쇄 원칙
- LSP(Liskov Substituion Principle): 리스코프 치환 원칙
- ISP(Interface Segregation Principle): 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle): 의존성 역전 원칙
위 원칙들은 결합도는 낮게 응집도는 높게라는 고전 원칙을 객체 지향의 관점에서 재정립한 것이라고 볼 수 있다..
** 결합도: 서로 다른 모듈간의 상호 의존 정도
- 결합도가 낮으면 모듈 간의 상호 의존성이 줄어들고 객체의 재사용이나 수정, 유지보수가 용이하다.
** 응집도: 하나의 모듈 내부에 존재하는 구성 요소들의 기능 적 관련성
- 응집도가 높으면 하나의 책엠이 집중하고 독립성이 높아져 재사용이나 기능의 수정, 유지보수가 용이하다
SRP(Single Responsibility Principle): 단일책임 원칙
어떤 클래스를 변경해야 하는 이유는 오직 하나 뿐이어야 한다.

위 그림의 남자 클래스는 역할과 책임이많다 => 코드에서 냄새가 난다.

위와 같이 역할을 분리한다면 다른 역할에서 어떤 불편함도 느끼지 않을 수 있다.
** 속성이 SRP를 지키지 않은 경우
class 사람 {
String 군번;
}
사람 로미오 = new 사람();
사람 줄리엣 = new 사람();
줄리엣.군번 = 123??????
위의 코드에 단일 책임 원칙을 적용해본다면 제일 상위 클래스로 사람을 두고 이를 상속하는 여자사람과 남자사람 클래스로 나누어 각자의 차이점만 구현할 것이다.
하나의 속성이 여러 의미를 갖는 경우는 단일 책임을 지키지 못하는 경우다.
예를들어 어떤 DB 테이블에 존재하는 하나의 필드가 토지인 경우에는 면적을 건물인 경우에는 층수를 나타내는 경우가 있어서 클라이언트 코드에서 if문을 여기저기 사용할 수 밖에 없던 일이 있었다.
이 같은 경우를 방지하기 위해서라도 테이블을 설계할 때도 단일 책임 원칙을 고려해야 한다. 이 같은 과정을 보통 정규화라고 하는데, 정규화를 조금 더 확장해서 생각해보면 결국 테이블과 필드에 대한 단일 책임 원칙의 적용이라고 할 수 있다.
** 메서드가 SRP를 지키지 않은 경우
class 강아지 {
final static Boolean 수컷 = true;
final static Boolean 암컷 = false;
Boolean 성별;
void 소변보다() {
if(this.성별 == 수컷) {
//한쪽 다리 들고 소변
} else {
//뒷다리 두개 굽혀 소변
}
}
}
위 코드는 소변보다 코드가 암컷 강아지와 수컷 강아지의 소변보다 메서드를 둘다 이용하려고 하는 메소드다. 이는 단일 책임 원칙을 위배하는 부분인데, 메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 대표적이 냄새가 바로 분기처리를 위한 if문이다.
abstract class 강아지 {
abstract void 소변보다()
}
class 수컷 강아지 extends 강아지 {
void 소변보다() {
// 한쪽다리 들고 소변
}
}
class 암컷 강아지 extends 강아지 {
void 소변보다() {
// 두 다리 굽혀 소변
}
}
위 코드와 같이 각 책임을 가진 클래스로 고쳐서 작성할 수 있다.
OCP(open closed principle): 개방폐쇄 원칙
** 자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다**
<개방 폐쇄="" 원칙을="" 사용하지="" 않은="" 경우="">
위와 같은 경우는 운전자라는 클래스가 소나타와 마티즈의 변화(창문 자동,수동 개방)에 따라 자신도 변화에야 하는 개방폐쇄 원칙을 어기고 있는 상황이다.
<개방 폐쇄="" 원칙을="" 사용한="" 경우="">
위와 같은 경우는 자동차라는 마티즈와 소나타의 위에 인터페이스, 혹은 상위 클래스를 두어 두 객체의 공통행동을 추상화하고 운전자는 추상화된 인터페이스, 혹은 클래스를 활용하여 다른 부분의 변화를 몰라도 된다.
\*\* 개방 폐쇄 원칙의 가장 좋은 예는 JDBC와 JVM이다.
JDBC 인터페이스를 통해서 클라이언트는 데이터베이스가 오라클에서 MySQL로 바뀌어도 커넥션을 설정하는 부분외에는 영향을 받지 않는다.
```
사람 김학생 = new 학생(****);
사람 김군인 = new 군인(****);
김학생.이름 -> 가능
김군인.이름 -> 가능
김학생.생일 -> 불가능
김군인.생일 -> 불가능
((학생)김학생)).생일 -> 형변환을 통해 가능
((군인)김군인).생일 -> 형변환을 통해 가능
```
빈약한 상위 클래스는 위 코드와 같이 여기저기 형변환을 발생시켜야 하므로 상속의 혜택을 제대로 누리지 못하고 지저분한 코드의 작성을 자아낸다.
## DIP(Dependency Inversion Principle): 의존성 역전 원칙
**고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화 된 것에 의존해야 한다.**
**추상화된 것은 구체적인 것에 의존하며 안 된다. 구체적이 ㄴ것이 추상화된 것에 의존해야 한다.**
**자주 변경되는 구체 클래스에 의존하지 마라.**
=> 자신보다 변하기 쉬운 것에 의존하지마라
- 자동차는 스노우 타이어에 의존한다.
- 해당 의존 관계를 타이어 인터페이스를 사용하여 역전시켰다: 구체적인 스노우 타이어에 의존하던 것을 추상적인 타이어에 의존하는 것으려 변경
```
// 기존 코드
class Car {
SnowTire tire = new SnowTire();
}
// DIP 적용
class Car {
Tire tire;
void setTire(Tire tire) {
this.tire = tire;
}
}
```
상위클래스, 인터페이스, 추상클래스 일 수 록 변하지 않을 가능성이 크다.
하위 클래스, 구체 클래스에 의존하지말고 더 추상적인 변하지 않는 것에 의존하라는것이 의존 역전 원칙이다.
```
참조:
https://sehun-kim.github.io/sehun/solid/
https://devcraft.tistory.com/28?category=749976
https://rira9817.blogspot.com/2019/03/5-solid.html
```
개방>개방>