# SOLID
- SRP (Single Responsibility Principle) 단일 책임 원칙
- OCP (Open Closed Principle) 개방 폐쇄 원칙
- LSP (Liskov Substitution Principle) 리스코프 치환 원칙
- ISP (Interface Segregation Principle) 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle) 의존 역전 원칙
# 의존? 역전? 그게 무슨말이야!
의존성이란 A클래스와 B클래스가 존재할때, A클래스가 B클래스를 아는경우 의존성있다고 말할 수 있다. A가 만일 구체적인 B클래스를 아는 경우, B클래스의 변경은 A의 클래스에도 어떠한 변화를 초래할 수 있다. A클래스가 B클래스의 영향을 받게 되는 것이다. 그리고 DIP에서는 이러한 것을 지양하라는 것이다.
이러한 클래스 사이의 의존성은 크게 4가지의 부류로 나 눌 수 있다.
## 연관 관계
A클래스 내에서 B클래스의 직접적인 객체 참조를 가진다. A는 B에 대해서 항상 알 수 있는 관계가 될 것이다.
class A {
public B b;
}
## 의존 관계
클래스 A의 함수 파라메타로 B클래스를 받게 된다. 이런 경우 A클래스는 B클래스에 대해서 일시적인 경우에 알 수 있는 의존 관계가 생긴다.
class A {
pubilc foo(B b){
return new B();
}
}
## 상속 관계
상속의 경우, B클래스의 필드와 메소드를 참조 할 수 있을 것이다 A는 B에 대해서 알 고 있다. 이 경우또한 B가 바뀐다면 A에 영향이 갈 것이다.
class A extends B{
}
## 실체화 관계
상속의 관계와 비슷하다고 생각하지만, B에서 정의한 인터페이스들 메소들이 바뀌어야 할 것이다. 그렇다면 A는 B의 변화에 영향을 받게 된다.
class A implements B{
}
이러한 의존의 관계가 올바른 설계 없이 일어난다면, 서로가 서로를 의존(양방향 의존)하면서 변화가 변화를 초래해 나에게까지 다시 영향을 입히고, 알 수 없는 관계가 생겨서 의존성을 주입하는 것에는 주의를 기울여야 한다.
그리고 우리가 사용했던 일반적인 절차지향적관계에서는 구체적인 것이 하위 수준의 모듈에 의존함을 알 수 있다. 이러한 의존관계를 역전 시켜야 한다는 것이 DIP에서 말하고 하는 바이다. 하나씩 알아가보자.
# 의존 관계 역전 원칙(DIP)
- 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.
- 추상화는 구체적인 사항에 의존해서는 안된다. 구체적인 사항은 추상화에 의존해야 한다.
전통적인 소프트웨어 구조에서는 상위 수준의 모듈이 하위 수준의 모듈에 의존하는 경향을 가진다.
상위 수준이 하위수준에 의존하고 있다. 이말은 즉, 상위 수준이 하위수준을 "사용"하고 있다는 말과 똑같다. 이해가 조금 안될 수 있다. 상위 수준이 하위 수준을 사용하는 경우 어떤 경우 일까?
class 상위수준 {
public 하위수준 low;
// ....
public void foo(){
low.doSomeThing();
}
}
대게 하위수준의 모듈에서는 구체적인 행동을 구현한다. low 클래스에서 db에서 어떠한 행동이 일어나게 되는 doSomeThing을 구현했다면, 상위수준(클라이언트)의 모듈에서는 이 모듈을 사용 할 것이다. 상위 수준의 모듈은 어플리케이션의 중요한 정책 의사결정과 업무 모델을 포함하고 있다. 이러한 상위 모듈이 하위 수준 모듈에 직접 의존할경우, 하위 수준의 모듈의 변경은 상위 수준 모듈에 직접적인 영향을 초래 할 것이다.
또한 상위수준의 모듈은 재사용을 하기를 원한다. 비슷한 행동을 하는 하위수준이 있다면, OCP에서 공부했던거와 같이 수정없이 추가만으로 이러한 모듈의 기능들을 재사용하고자 할 것이다. 그러기 위해서는 의존성 역전이 필요하며 그 본질에는 추상화가 있다는 것이 의존 관계 역전 원칙(DIP)에서 말하고자하는 핵심이다.
## Policy - Mechanism -Utility 예제
클린 소프트웨어에서는 이러한 추상화의 사용을 Policy - Mechanism -Utility예제로 설명하고 있다.
정책을 결정하는 최상위 Policy모듈에서는 Mechanism에 구현된 것을 이용할 것이다. Mechanism에 구현된 많은 것들은 Utility의 모듈의 도움을 받아서 구현 했을 것이다. Policy는 Mechanism에 의존하고, Mechanism은 Utility에 의존하고 있다. 좋지 않은 경우 Uitlity의 변경은 Policy에서 어떠한 오류를 초래 할 수 있다.
구현 레이어에 직접 의존하지 않고, 추상화 된 인터페이스에 의존하게 한다. 각 수준의 Layer는 그 수준에서 필요한 서비스를 추상화된 인터페이스로 구현하고 이에 의존한다. 그리고 하위 수준의 Layer는 이 인터페이스를 실체 구현화한다. 상위 수준의 레이어는 하위 수준에 레이어에 더 이상 의존하지 않게 된다. 또한 하위 수준의 레이어는 이제 상위수준의 추상화된 인터페이스에 의존하게 된다. 이로써 의존성이 역전된다. 위 UML에서는 Policy와 Mechanism을 보면서 설명을 이해해 볼 수 있다.
이전 그림에서는 상위수준인 Policy가 하위 수준의 Mechansim에 직접 의존하고 있었지만, 이를 추상화된 인터페이스(Policy Service)에 의존하면서 하위 수준에 더 이상 의존 하지 않아도 된다. Policy Service Interface는 Policy에서 필요한 행위를 추상화 한 것이기 때문에 같은 레이어에 속할 것이다. Mechansim은 Policy에 의해서 사용당하고(의존당하고)있었지만, 이제는 Policy Service Interface를 구현하는 구체화가 되면서 더이상 의존당하지 않고, 의존하는 관계(역전)가 됐다.
이렇게 의존성을 역전시키면서, 소유권이 역전 됐고 좀 더 유연하고 튼튼하고 이동이 쉬운 구조를 만들어 낼 수 있었다.
## 결국 추상화
결국 DIP에서는 추상화에 의존하라는 것이다. 추상화에 의존하게 되는 경우 의존성의 역전이 일어 날 것이다. 즉, 구체 클래스(concrete)에 의존하지 말고, 그 중간에 추상클래스나 인터페이스를 두어 이것을 매개체 마냥 의존해야 한다는 것이다.
- 어떤 변수도 구체 클래스에 대한 포인터나 참조값을 가져선 안된다.
- 어떤 클래스도 구체 클래스에서 파생되어서는 안 된다.
- 어떤 메소드도 그 기반 클르새에서 구현된 메소드를 오버라이드해서는 안 된다.
이것들은 물론 위반 되는 경우도 있지만 대체로 지켜야 한다는 것이다.
구체적이긴 하지만 변경이 일어나지 않는 비휘발적인 클래스의 경우 또 그 클래스에 대해서 더 이상 파생클래스가 생성되지 않는다면 이것에 의존하는 건 그렇게 큰 문제를 야기하지는 않는다.
## Button - Lamp 예제
버튼의 poll()이 호출되면, 소유하고 있는 Lamp의 turnOff() 또는 turnOn()이 실행 될 것 이다. 문제 없어 보이지만, Button은 구체화된 Lamp클래스에 의존하고 있다. 중간에 추상클래스나 인터페이스를 두고, Button은 이 인터페이스를 소유하고, Lamp도 해당 인터페이스를 소유하도록 해야 한다. Lamp의 의존관계는 위에서 이야기했던 대로 역전 될 것이다.
여기서 중요한 건, SwitchableDevice에 대한 이름이다. 이 인터페이스를 ButtonInterface라고 이름을 짓을 경우 이름에 의존성이 남게 된다. 그래서 ButtonInterface가 아닌 SwichableDevice와 같은 이름으로 이름을 지음으로써, turnOff()와 turnOn()의 기능을 수행 할 수 있는 것들이 범용 적으로 사용할 수 있도록 바꿔줄 수 있다.
# 마치며
결국 구체적은것에 의존하지말고, 중간에 추상클래스나 인터페이스를 두어 의존하게하고, 하위 클래스는 해당 추상클래스나 인터페이스에 의존하게 하라는 것이다.
의존성이 역전된다면 객체지향적인 설계이며, 의존성이 역전되어 있지 않다면 절차적인 설계이다.
의존성의 역전은 재사용성을 증대해주기도 하고 변경에 대한 취약점을 막아준다. 추상화와 구체 클래스가 서로 분리되어 있기 때문이다.
UML그리다가 눈 빠질것 같다..
'• 독서 > Design Pattern' 카테고리의 다른 글
[클린소프트웨어#8] SOLID-인터페이스 분리 원칙(Interface Segregation Principle) (0) | 2022.10.20 |
---|---|
[클린소프트웨어#6] SOLID-리스코프 치환원칙(Liskov Substitution Principle) (0) | 2022.10.20 |
[클린소프트웨어#5] SOLID-개방 폐쇄 원칙(Open Closed Principle) (0) | 2022.10.19 |
[클린소프트웨어#4] SOLID-단일 책임 원칙(Single Responsibility Principle) (0) | 2022.10.19 |
[클린소프트웨어#3] 애자일한(Agile)설계를 위한 방법과 7가지 부패한 특성 (1) | 2022.10.18 |