티스토리 뷰

객체 지향 설계의 정수 5원칙 SOLID

 

SRP : 단일 책임 원칙

OCP : 개방 폐쇄 원칙

LSP : 리스코프 치환 원칙

ISP : 인터페이스 분리 원칙

DIP : 의존 역전 원칙

 

SOLID를 잘 녹여낸 프로그램은 이해하기 쉬우며 리팩토링과 유지보수가 수월해지며 논리적으로 정연됩니다. SOLID는 객체지향의 4대원칙 캡상추다(줄임말..)를 발판으로 하고 있으며 디자인 패턴의 뼈대, 스프링 프레임워크의 근간이기도 합니다.

 

 

1) SRP - 단일 책임 원칙

 

어떤 클래스를 변경해야 하는 이유는 1가지 여야 한다.

 

예시를 들자면 남자라는 클래스에 여자친구, 어머니, 직장상사 등등과 접목하는 메서드있다고 칩니다. 그런데 여자친구와 헤어져서 여자친구와 관련된 데이트하기 메서드가 무색해집니다. 쓸모가 없어지는 것이죠.

 

그렇기에 역할과 책임에 따라 남자 클래스를 남자친구, 사원, 아들 등등으로 쪼갠다면? 이해하기 쉬울 뿐만 아니라 다른 목적의 클래스와도 상관이 없어지게 됩니다.

 

물론 단일 책임 원칙에는 변수한테도 적용되는데,

 

사람{

   String 군번;

}

 

사람 -> 남자, 사람 -> 여자 라는 클래스가 이런 식으로 구성되어 있을 때 문제가 보이시죠?

남자는 군번이 필요하지만, 여자는 필요없죠.(물론 군대를 간다 안간다 대한민국 상식기준으로서 설명입니다.) 하나의 속성이 여러개의 뜻을 가지거나 필요없는 부분이 생긴다면 SRP를 위반한다고 봐도 무방합니다.

 

메서드 역시 SRP를 위반할 수 있는데.

 

강아지{

   

     boolean 성별; / true면 남자, false면 여자

 

     void 소변보다( ){

         if(this.성별){ ... }

         else{ ... }

     }

}

 

이런 식으로 소변보다 메서드가 암/수컷의 강아지의 행동을 분기처리하여 모두 구현하려고 하기에 위반됩니다.

이것을 수정하려면 강아지 추상화 클래스에서 상속을 받아 소변보다 메서드를 재정의 해주어 리팩토링할 수 있습니다.

 

 

2) OCP - 개방 폐쇄 원칙

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하지만 변경에 대해서는 닫혀야 한다.

=> 자신의 확장에는 열려있고 주변의 변화에 대해서는 닫혀야 한다.

 

예를들어,

 

운전자 -> 마티즈{ 창문수동개방( ), 기어수동조작( ) }

운전자 -> 쏘나타{ 창문자동개방( ), 기어자동조작( ) }

 

이렇게 클래스가 구성이 된다면 운젖나는 차량에 따라 운전하던 모든 습관을 바꿔야 합니까?

 

그렇다면 이건 어떻습니까?

 

운전자 -> 자동차 { 창문개방 ( ), 기어조작( ) } + 자동차를 상속받는 마티즈와 소나타

 

이런식으로 상위 클래스 또는 인터페이스를 중간에 둠으로써 다양한 자동차가 생긴다고 해도 객체 지향 세계의 운전자는 운전 습관에 영향을 받지 않습니다. 이 뜻은 자동차 입장에서의 확장은 자유롭고, 운전자 입장에서는 닫혀있는 것입니다.

 

JDBC 또한 이러한 구조를 가지는데요.

 

https://codedragon.tistory.com/5960

 

JDBC 구조, JDBC의 구조와 역활

JDBC 구조 JDBC는 네트워크상에 있는 데이터베이스에 접속할 수 있도록 해주는 일종의 데이터베이스 연결기능을 제공하며 JDBC API, JDBC드라이버, JDBC드라이버 관리자등으로 구성되어 있습니다.   

codedragon.tistory.com

 

JDBC 인터페이스를 통해 데이터베이스가 MS SQL -> ORACLE로 바뀌더라도 커넥션 설정 부분외에는 건드리지 않아도 되는 장점을 가집니다.

 

 

3) LSP - 리스코프 치환 원칙

서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.

 

상속에서 객체 지향에서의 상속은 조직도나 계층도가 아니라 분류도가 되어야 한다고 했습니다. 2가지 조건이 있는데

 

1. 하위 클래스 is a kind of 상위 클래스

2. 구현 클래스는 is able to 인터페이스 

 

예시를 들겠습니다. 아버지가 상위 클래스이고 딸이 하위 클래스라면

 

아버지 춘향 = new 딸( ); // 계층도/조직도

 

딱 봐도 이상합니다. 그냥 딸의 역할을 아버지에게 전달이 가능한지도 의문입니다.

 

 

동물이 상위 클래스 이고, 펭귄이 하위 클래스라면?

 

동물 황제펭귄 = new 펭귄( ); // 분류도

 

오 괜찮은 로직이 구성된 듯 합니다. 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야 한다는 원칙에도 부합하죠.

 

4) ISP - 인터페이스 분리 원칙

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.

 

SRP개념과 비슷하지만 분리를 인터페이스로 한다는 것이 차이점입니다. 약간의 차이점은 SRP로 만들경우 상위 클래스가 풍족해야 좋고, 인터페이스는 목적에 맞는 것만 있으면 좋습니다.(상대적으로 적을 수록)

상위 클래스가 풍족할 경우 형변환을 하는 것을 줄일 수 있죠. ( 상위 클래스에 다있기 때문에 )

 

인터페이스의 최소주의 원칙은 왜 좋을까요? 이는 SRP로 문제를 해결하기 전에 남자 클래스를 생각하면 됩니다. 가지각색의 인터페이스를 다 갖다 붙이면 결국 해결하기 전에 문제로 회귀하게 됩니다. 그러니 최소한의 기능만 공개하는 것이 중요하게 작용합니다.

 

 

5) DIP - 의존 역전 원칙

 

고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈은 다른 추상화 된 것에 의존해야 한다.

추상화 된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화 된 것에 의존해야 한다.

자주 변경되는 구체 클래스에 의존하면 안된다.

 

만약 자동차 클래스가 스노우 타이어에 의존한다면, 다른 일반타이어나 광폭타이어는 어떻게 설계할까요?

이 때부터 코드가 더미코드가 되기 시작할 것입니다 그렇다면 어떻게 구성하는 것이 좋을 까요?

 

자동차 -> 스노우타이어 (X)

 

자동차 -> 타이어 인터페이스 <-- { 스노우, 일반 ,광폭 타이어 }

 

자동차 클래스는 타이어라는 인터페이스를 의존하게 하고 타이어 인터페이스는 하위 개념으로 스노우, 일반, 광폭타이어를 가지게 한다면? 변경의 영향을 최소화 할 수 있습니다. 이것이 의존의 방향을 역전하는 방식입니다.

 

 

이상으로 5장을 마치고 6장 디자인 패턴으로 돌아오겠습니당~~~~!

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함