[클린소프트웨어#4] SOLID-단일 책임 원칙(Single Responsibility Principle)
# SOLID 올바른 객체지향설계와 애자일한 설계를 위해서는 Martin이 제안한 5가지의 원칙(SOLID)을 준수해야 한다. 그러면 우리는 변경에 유연한 소프트웨어를 만들 수 있다. 5가지의 원칙중 SRP 단일
devforyou.tistory.com
# 사담
얼마전 카카오데이터센터에서 화재가 발생하여 카카오 연계 서비스들이 마비가 됐었다. 티스토리도 영향을 받아서 서비스가 원할하지 못했다. 특히 티스토리 스킨들도 사라지고 플러그인같은것도 사용을 못해서 [이전글]을 심을 수 없었다. 그리고 글을 작성하는 지금 시점에서 90%의 서비스들이 되돌아 온 것 같다. 티토리가 작동하지 않아서 구글링도 못했는데.. 생각보다 카카오에 엄청난 의존을 하고 있구나 느꼈다. 괜히 서비스가 정상 작동하여 기분이 좋다ㅎㅎ
# 시작하며
- SRP (Single Responsibility Principle) 단일 책임 원칙
- OCP (Open Closed Principle) 개방 폐쇄 원칙
- LSP (Liskov Substitution Principle) 리스코프 치환 원칙
- ISP (Interface Segregation Principle) 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle) 의존 역전 원칙
# 개방 폐쇄 원칙 (Open Closed Principle)은 최종 도착지이다.
개방 폐쇄 원칙 이름만 가지고는 무슨 뜻을 가지고 있는지 불분명하다. 나도 처음 들었을때 그리고 해당 챕터를 처음 읽었을때까지만 해도 Open과 Closed로 표현한 이유를 알지 못했다. 개방 폐쇄 원칙에 대해서 자세한 설명을 하기 전, 우리의 애자일한 설계 그리고 변경에 유연한 객체지향 소프트웨어를 디자인하는데 최종 도착지는 개방 폐쇄 원칙(OCP)를 만족 시키는 것이다. 왜 그런지 개방폐쇄 원칙을 하나씩 알아보도록 하자.
# Open & Closed
개방(Open)은 패쇄(Closed)는 무엇이란 말인가? 이것은 OCP가 지니는 두가지 주요 속성을 말한다.
## 확장에 대해서는 열려 있어야 한다.
우리가 열심히 개발을 하고 소프트웨어를 만들고 있다고 가정하자. 기존의 설계대로 만들었는데, 고객이 요구사항의 변경을 요청한다. 새로운 요구사항을 추가해야하는 상황이 왔다. 그러면 그 변경을 따르기 위해 새로운 행위를 모듈을 추가할 수 있어야 한다는 것이다. 즉 변경은 가능하고 자유롭게 일어나야 한다.
## 수정에 대해서는 닫혀 있어야 한다.
그러나 이 변경사항을 적용하기 위해 추가한 행위는 그 모듈의 소스코드나 바이너리 코드의 변경을 초래하면 안된다는 것이다. 이해가 잘 가지 않을 수 있다. 그리고 이 두가지가 같이 일어난다는게 가능한 것인가라는 생각이 들 수 도 있다.
개방 폐쇄 원칙을 만족하지 못하는 것을 개방 폐쇄 원칙을 지키도록 수정해보는 것으로 이해보자.
# Client - Server 예제
클라이언트-서버의 관계를 살펴보자. 클라이언트는 특정 서버에 대해서 가지고 있다. 구성의 관계가 될 것이다.
class Client {
Server myServer
}
이런 관계 일때 문제가 없다고 생각하겠지만, 여러개의 서버를 사용해야 한다고 생각해보자! 한개의 서버를 추가해야하는 상황이 온다면 어떻게 해야 할까?
class Client {
myServer myServer
DBserver myDbServer
}
새롭게 들어온 추가사항에 대해서 우리는 어떻게 대처 했을까 생각해보자. Client클래스의 코드를 수정하여 새로운 db서버를 가지게 했으며, 이에 해당하는 추가 메서드들이 구현 될 수 도 있을 것이다. 그렇다면 10개의 서버를 더 추가해야한다면 계속해서 이런 방식으로 코드를 추가하는건 어딘가 이상하다. OCP를 만족하지 못했다는 생각이 든다.
## 추상화를 통해 해결하기
위 예시에서의 문제는 Client,Server 클래스 모두 구체적(concrete)인 클래스 였다. 그렇기 때문에 추상화 되지 않았고 다형성을 사용할수도 없다. Server의 기능을 추상화 하여 추가에도 자유롭게 해주자.
myServer와 DbServer는 인터페이스를 상속받아 공통의 기능을 구현한다. Client는 다형성을 사용하여 코드를 수정하지 않더라도 myServer와 DbServer를 사용할 수 있다. Client는 myServer와 DbServer가 뭐든 중요하지 않다. 그저 기능이 구현되면 되기 때문이다.
class Client {
ClientInterface server
public void setServer(ClientInterface server) {
thise.server = server
}
}
// main 코드
public static void main(){
ClientInterface dbServer = new DBserver();
ClientInterface myServer = new myServer();
Client.setServer(dbServer);
Client.setServer(myServer);
}
위와 같이 추상화를 통해서 분리한다면 Client코드에 별다른 수정 없이도 새로운 변경사항(새로운 서버클래스)요구가 있을때면 클래스를 추가하기만 하면 된다. 이것이 OCP의 전부인 셈이다.
그리고 위 예제는 전략패턴과 아주 깊은 관련이 있다.
[디자인패턴] 전략패턴 (Strategy Pattern)
# 설명 - 전략패턴 적용 정책이나 알고리즘을 교체하여 유연하게 사용 가능함 if-else문과 같은 것이 없어질 수 있음 한 분야(행동)에 대한 다양한 알고리즘이 존재하면, 이 알고리즘을 캡슐화
devforyou.tistory.com
# Shape 예제
교재에서는 Shape예제를 들어 OCP에 대해서 설명한다. 그리고 OCP는 모든것에 대해서 닫혀있을 수는 없다는 것을 설명한다. 어떻게 그런 일이 일어나는지 간단하게 살펴보자.
Square과 Circle을 추상화하여 Shape라는 추상 클래스를 만들고 도형들은 Draw()라는 메서드를 통해서 어딘가에 그려지는 기능을 수행해야 한다. 위에서 배운대로 추상화를 마쳤다고 가정해보자.
다음과 같은 상황에서, Shape를 상속받아 Triangle 클래스를 만들어 사용하려해도 별 문제 되지 않을 것이다. OCP에 만족하면서 새로운 클래스를 추가하고, 클라이언트 코드에 대한 수정은 없을 것이다.
즉, 경직성, 부동성, 취약성이 발견 되지 않는다. 기존 코드를 변경하기보다는 새로운 코드를 추가하는 방법으로 변경했다는 것이다.
## 모든 것에 닫혀 있을 수는 없다.
그러나 DrawAllShapes는 Circle과 Square가 있다면 Circle은 모든 Square앞에서 그려져야 해야한다고 생각해보자. 그럴 경우 DrawAllShapes()에서는 Circle에 대한 모든 검색을하고 도형을 그리고 그 후에 Square를 그려야 할 것이다.
우리가 위에서 그렸던 Shape의 추상화한 것은 이번에는 도움되는 추상화가 아니라는 것이다. 순서가 중요할 수 있는 새로운 추상화 방법을 모색해야 한다는 점이다.
즉 모듈을 아무리 닫혀있게 만들어도 닫혀 있지 않은 것은 분명히 존재 할 것이고, 이러한 모든 상황에 만족하는 모델은 없다는 것이다. 폐쇄는 완벽할 수 없기에 전략적이어야하고, 자신의 설계 자신의 상황에 맞는 변경을 추측하고 그 변경에 대해 보호할 수 있는 추상화를 작성해야 한다는 것이다.
그렇다면 이러한 변경을 추측하는 가장 좋은 방법은 무엇일까? 바로 변경이 일어날 때까지 기다린다는 것이다. 그리고 그 변경을 최대한 빨리 일어날 수 있도록 변경을 촉진 시킨다. 변경에 대비해서 미리 코드를 작성하면 불필요한 복잡성이 야기 될 수 있다. 그렇기 때문에 첫 변경에 대해서는 그대로 맞이하고 그 이후에 리팩토리를 거쳐서 같은 상황에데해서는 OCP를 만족시킬 수 있도록 전략을 세우는 것이다.
## 변경을 촉진하는 법
- 이러한 변경을 촉진하는 방법도 있다.
- 테스트주도 개발하기
- 짧은 주기로 개발하기
- 기반구조보다 기능 요소를 먼저개발하기 그리고 이를 이해당사자에게 보여주기
- 가장 중요한 기능요소를 먼저 개발하기
- 소프트웨어를 빨리 자주 릴리즈하기
# 마치며
결국 OCP는 우리가 달성해야하는 목표이고, 모든 사항에대해서 OCP를 만족시키는 설계를 할 수 없다는 것이다. 그래서 이러한 상황을 최대한 빨리 맞이 할 수 있도록 변경을 촉진시켜서 리팩토링을 거쳐 OCP를 만족시키는 쪽으로 개선해 나가는 것이다.
'• 독서 > Design Pattern' 카테고리의 다른 글
[클린소프트웨어#7] SOLID-의존 관계 역전 원칙(Dependency Inversion Principle) (0) | 2022.10.20 |
---|---|
[클린소프트웨어#6] SOLID-리스코프 치환원칙(Liskov Substitution Principle) (0) | 2022.10.20 |
[클린소프트웨어#4] SOLID-단일 책임 원칙(Single Responsibility Principle) (0) | 2022.10.19 |
[클린소프트웨어#3] 애자일한(Agile)설계를 위한 방법과 7가지 부패한 특성 (1) | 2022.10.18 |
[클린소프트웨어#2] 익스트림프로그래밍(XP)에 대해서 (0) | 2022.10.18 |