본문 바로가기
개발자 전향 프로젝트

[Spring] 객체지향 설계 5 원칙 SOLID란? feat.클린코드

by 샘오리 2022. 9. 21.
객체지향 설계의 핵심은 자고로
응집도는 높고 결합도는 낮은
프로그램을 설계하는 것이다.

 

그렇다면 응집도는 무엇이고 결합도는 또 무엇일까?

[ 응집도(Cohesion) ]

한마디로 내부 요소들의 연관성이다.

예를 들어 하나의 클래스에서 함수(method)와 변수가 통일된 목적을 가지고 연관되어 있다면 응집도가 높은 것이고

이 함수는 이 변수에 저 함수는 저 변수에 뒤죽박죽이라면 그 클래스는 다양한 목적이 있는 클래스가 되고

목적을 기준으로 더 쪼개질 수 있으므로 단일 목적으로서의 응집도가 낮다고 할 수 있다.

 

더 쉬운 예시:

메뉴가 하나뿐인 오래된 음식점을 생각해보자. 그 음식점의 모든 내부 요소들은 그 하나의 메뉴를 만드는 것이라는 목적으로 똘똘 뭉쳐서 연관성이 매우 높을 것이다. 

반면 메뉴가 여러개인 음식점들은 내부 요소들이 각각 의존하는 게 다르기 때문에 응집도가 낮다고 할 수 있다.

 

[ 결합도(Coupling) ]

객체지향의 관점에서 결합도는 객체 또는 클래스가 협력에 필요한 적절한 수준의 관계만을 유지하고 있는지를 나타낸다. 만약 객체의 인터페이스가 제대로 분리되어 있지 않고, 불필요하게 많은 정보를 알고 있다면 이는 결합도가 높은 것이다.

반면 인터페이스와 구현체가 제대로 분리되었다면 클라이언트는 인터페이스만 접근(의존)하면 되는 것이고 구현체가 바뀔 때 마다 클라이언트는 바뀔 필요가 없는 것이다.

 

클라이언트: 요청자

서버: 응답자

 

예: 클라이언트(요청자)인 UserService 에서 서버인 UserRepository라는 interface만 알면

그 interface를 통해 다양한 목적에 맞게 구현된 구현체들은 일일히 알아야 할 필요도 없고 

나중에 그 구현체들에게서 문제가 생기거나 변경사항이 생기더라도 그 구현체들은 UserRepository에 연결되어 있고

클라이언트인 UserService는 UserRepository와 연결되어 있기 때문에 클라이언트(요청자)의 소스코드는 바뀔 필요가 없는 것이다.

 

더 쉬운 예시: 당신은 집에 있는 데스크탑 키보드에 커피를 쏟아서 키보드가 망가졌다.

키보드를 교체/구매 하기 위해 온라인으로 새로운 키보드를 구매해서 고장난 키보드를 교체했다.

그 키보드를 사용하는 당신은 당신의 키보드가 다른 것으로 바뀌었음에도

키보드의 주 목적이자 역할인 타자를 치는 것에 대해서 배울 필요가 없다. 왜? 이미 알고 있기 때문에. 

(기계식 키보드의 특수 단축키를 통한 조명을 변경하는 것 따위는 예외)

 

만약 키보드를 살 때 마다 그 키보드를 사용하는 방법을 다시금 배워야 한다면

얼마나 귀찮겠는가?

바로 그것이 백로직이 바뀔 때 마다 클라이언트가 바뀌어야 한다면 생기는 불편함일 것이다.

 

이를 방지하기 위해 객체 지향 설계를 한 것이고 

구현체가 바뀌더라도 추상화 클래스인 interface만 의존한다면 신경 쓸 필요가 없도록 해놓은 것이다.

클라이언트가 서버의 interface를 알고 있다는 것은 그 구현체의 가장 기본적인 역할을 알고 있는 것과 같은 원리이다.

 

키보드는 타자를 치기 위함이고 마우스는 클릭을 하기 위함 등등.. 

 


응집도는 높고 결합도는 낮은 프로그램이 좋은 프로그램을 만들기 위해

위 내용을 조금더 자세하게 풀어서 규칙으로 만든 것이 바로 이  SOLID이다.

1. 단일 책임 원칙(Single responsibility principle) : 

SRP

하나의 클래스는 하나의 책임을 가져야 한다. 책임을 기능이라 이해하면 쉽다.

하나의 클래스에 목적이 다양하면 위 원칙에 위배된다 -> 클래스를 더 잘게 쪼개거나 분리한다.

커피 로봇은 커피만 만들면 된다

2. 개방 폐쇄 원칙(Open/closed principle) : 

OCP

클라이언트의 코드는 확장(추가)는 할 수 있어도 변경을 하면 안된다.

백로직이 바뀐다고 클라이언트의 소스코드를 다시 엎어야 한다-> 위 원칙에 위배된다.

-> 클라이언트는 추상 클래스에만 의존하도록 하자.

3. 리스코프 치환 원칙(Liskov substitution principle) : 

LSP

인터페이스의 하위 구현 클래스들은 인터페이스에서 정한 함수의 이름과 관련된 기능을 구현해야 하지

아무리 오버라이딩해서 바꾼다고 하더라도 전혀 상관없는 기능으로 쓰거나 정반대의 기능으로 사용한다면 혼돈을 야기할 것이다.

예: 누가 봐도 더하기는 더하기고 빼기는 빼기인데 더하기라는 이름의 함수를 쓰고 빼는 기능을 두는 것은 금지하자.

-> 이름과 기능의 mismatch를 줄이자


4. 인터페이스 분리 원칙(Interface segregation principle) : 

ISP

 

범용 인터페이스 하나로 두지 말고 인터페이스도 더 상세한 목적에 맞게 철저하게 쪼개자

5. 의존관계 역전 원칙(Dependency inversion principle) : 

DIP

클라이언트는 인터페이스에만 의존하도록 하자.

구현 클래스에 의존하는 순간 구현 클래스가 바뀔 때 마다 클라이언트도 변경되어야 한다. 

 

예를 들어서 당신이 드라마 작가이고 탐정역할을 해줄 주인공이 필요한데

주인공으로 캐스팅한 A라는 배우가 개인사유로 인해 역할을 소화하기 힘들다고 해서

B라는 배우로 대신 섭외했는데 배우가 바뀌었다고 드라마 대본이나 시나리오가 바뀐다면 어떨까?

혼돈이다.

이런일을 방지하기 위해 드라마 대본은 탐정역할만 알면 되고

누가 될지 모르는 배우 후보들은 탐정역할만 알면 된다.

 

역할인 interface가 클라이언트와 구현 클래스 사이의 중간다리가 되는 셈이다.

중간다리의 완벽한 예시: 해리포터 부엉이