객체 지향 프로그래밍/OOP

객체지향 설계원칙 이해하기

ghtis1798 2021. 3. 7. 11:50

객체 지향 설계 5원칙

1. SOLID란?

 

이전 포스팅에서 객체 지향 특성에 대해 다루어 보았습니다.

캡슐화, 상속, 추상화, 다형성으로 인한 객체 지향의 장점을 이해했습니다.

그런데 '설계 5원칙'은 또 무엇일까요? 🤦‍♂️

약자로 'SOLID'라고 써놓으니 더 어렵게 느껴지는 것 같습니다.

쉽게 말하자면 설계 5원칙이란 '객체 지향 사용법'입니다. 👀

학습한 '객체 지향 특성'이 객체 지향 프로그래밍을 위한 '도구'였다면,

'객체 설계 5원칙'은 그 도구를 사용하는 '도구 사용 방법'인 것이죠. ✨

우리는 이 5가지 원칙을 지키면서 프로그램을 작성할 것입니다.

그 프로그램은 이해하기 쉽고, 유지보수 하기 용이하며, 논리적일 가능성이 높습니다. 👏👏👏

 

1.1. 단일 책임 원칙(SRP)

 

단일 책임 원칙은 하나의 클래스는 하나의 책임과 역할만 갖도록 한다는 것입니다.

하나의 클래스가 여러 역할과 책임을 갖는 예를 들어 보겠습니다.

어떤 남자는 누군가의 애인이며, 누군가의 친구이면서, 누군가의 부하직원입니다.

SRP를 준수하지 않은 경우

그런데 남자가 애인과 헤어져 솔로가 되었습니다. 😯

데이트도, 스킨십도 하지못하는 남자는 우울해 친구와 게임도 하지 않고, 술먹고 학교 동창에게 실수도 합니다.

심지어 직장에서는 보조 업무 중 안하던 실수에 업무보고도 까먹습니다.

불쌍한 남자는 일상생활이 어려운 지경에 이르게 되죠. 🤦‍♂️🤦‍♂️

그래서 남자를 애인, 친구, 부하직원에 맞춰 하나의 역할만 수행하도록 분리하는 것이 SRP입니다.

SRP를 적용한 경우

남자는 이제 애인과 헤어지더라도 일상 생활에 지장을 받지 않습니다. 😃

클래스로 예시를 들다보니 '객체 지향 특성' 중 '추상화' 과정과 긴밀하게 연결되어 있네요.

'추상화'의 결과물이 클래스이기 때문입니다.

클래스를 예시로 들었지만 속성, 메서드, 모듈 등에도 적용할 수 있습니다.

 

1.2. 개방 폐쇄 원칙(OCP)

 

개방 폐쇄 원칙은 자신의 확장에는 열려 있고, 주변의 변화에는 닫혀 있어야 한다는 원칙입니다.

OCP를 준수하지 않은 예시를 보겠습니다.

OCP 적용 전

낚싯꾼은 바다 낚시를 하다가 민물 낚시를 하려면 낚싯대를 바꿔야합니다.

낚시꾼은 낚싯대를 변경할 때마다 영향을 받게 되는 것이지요.

낚시꾼이 어떤 낚싯대를 이용하던 영향을 받지 않도록 하기 위해선 다음과 같이 구현해야 합니다.

OCP 적용 후

낚싯대가 계속 변하면서 낚시꾼에게 영향을 주었어요. 😕

그래서 변하지 않는 낚싯대를 바다 낚싯대와 민물 낚시대가 상속하도록 만들었습니다.

낚싯대를 고정하고, 낚싯대(자신)에 대해서는 확장한 것에서 두 가지를 느꼈습니다.

첫 번째는 더 이상 낚시꾼은 낚싯대에 영향을 받지 않아요. (주변 변화에 폐쇄)

두 번째는 낚싯대(자신)에 대해서는 확장했다는 것이죠. 😮

자바의 JVM을 예시로 들 수도 있을 것 같습니다.

JVM은 OS가 맥이던 윈도우던 잘 작동하도록 만들어주죠.

자바가 어떤 컴퓨터, 어떤 OS 상에서도 문제 없이 작동하도록 말입니다.

OCP는 자기 자신의 확장에 대해서는 열려 있고 주변의 변경에 대해서는 닫혀 있다고 했습니다.

자바는 JVM으로 인해 맥과 윈도우로의 확장이 열려있습니다.

동시에 주변의 컴퓨터 변경(하드웨어적, 소프트웨어적)에 영향을 받지 않습니다.

 

1.3. 리스코프 치환 원칙(LSP)

 

안타깝게도 리스코프 치환 원칙은 이름이 와 닿지가 않네요. 🤷‍♀️

리스코프 치환 원칙은 '하위 타입이 상위 타입을 대체할 수 있어야 한다.'는 것입니다.

상속과 관련이 있을 것 같은 느낌이 들지 않나요? 👀

이전 포스팅에서 상속은 '확장 혹은 분류'의 개념과 유사하다고 했습니다.

고양이는 품종에 따라 페르시안, 먼치킨, 러시안블루 등으로 '분류'될 수 있죠. 😃

따라서 페르시안, 먼치킨, 러시안블루는 고양이의 공통된 속성과 행위를 상속받고,

각자 자신의 종에 맞는 고유한 속성, 행위를 정의하는 것이죠.

리스코프 치환 원칙을 잘 지킨 프로그램은 하위 클래스가 상위 클래스의 한 종류가 됩니다.

동시에 분류된 클래스는 인터페이스 메소드를 구현해서 사용할 수 있어야 합니다.

결과적으로 리스코프 치환 원칙은 '상속'을 준수하면 자연스럽게 만족됩니다.

 

1.4. 인터페이스 분리 원칙(ISP)

 

인터페이스 분리 원칙(ISP)은 자신이 사용하지 않는 메서드와 의존 관계를 맺지 않는다는 원칙입니다.

ISP는 SRP에서 하나의 클래스가 여러 가지 역할을 갖고 있던 문제를 다른 방법으로 해결합니다.

단일 책임 원칙(SRP)의 솔로가 된 남자 예시를 다시 보겠습니다.

SRP 적용 전 문제점

ISP는 각각의 애인, 학교 동창, 직장 상사가 자신과 관련 있는 메소드만 사용하도록 '제한'합니다.

바로 Interface를 사용해서요. 😎

ISP 적용 후

하지만 단일 책임 원칙(SRP)를 따르는 것이 일반적이라고 합니다.

'인터페이스 최소주의 원칙'이라는 원칙에 의해서 그렇습니다.

원칙이 너무 많이 나오니 어렵네요. 🤦‍♂️

한 줄로 표현하면 상속을 늘리고, 인터페이스로 메서드를 구현하는 것은 줄이라는 얘기입니다.

상식적으로 상속의 내용이 많아지면 내가 직접 작성할 코드의 양이 줄어듭니다.

그대로 가져다 쓰면 되기 때문이죠. 👍👍

하지만 인터페이스로 제공받는 경우 일일이 구현해야합니다.

그래서 인터페이스보다는 상속을 많이 이용하라고 했다는 생각이 듭니다.

정리해보면 SRP는 하나의 클래스가 하나의 책임과 역할만을 갖도록 분리해서 구현했습니다.

반면 ISP는 분리하지 않고 하나의 클래스로부터 인터페이스를 통해 메소드를 제한했습니다.

 

1.5. 의존 역전 원칙(DIP)

 

마지막 의존 역전 원칙입니다.

역전이라는 말이 처음에 어렵게 느껴졌습니다.

생소한 용어에 대한 막연한 두려움이 있는 것 같아요. 😐

의존 역전 원칙은 '자신보다 쉽게 변하는 것에 의존하지 않는다'는 것입니다.

디자인 원칙 개발자인 로버트 C. 마틴은 다음과 같이 말했다고 합니다.

'구체적인 것이 추상화된 것에 의존해야 하며 그 반대가 되어선 안된다.'

예를 들어, 어부가 낚싯대와 잉어 미끼를 샀다면 다음과 같은 의존관계가 있습니다.

DIP 적용 전

어부가 만약 잉어가 아니라 육식 어종인 베스를 잡고 싶다면 베스 미끼를 사야합니다.

이 때 잉어 미끼를 베스 미끼로 교체할 때 낚싯대는 영향에 노출되어 있습니다.

이것은 낚싯대가 자신보다 더 자주 변하는 잉어 미끼에 의존하기 때문에 생기는 문제입니다.

따라서 낚싯대가 구체적인 잉어 미끼가 아닌 추상화된 미끼 인터페이스에 의존하도록 만들겠습니다.

DIP 적용 후

이제 미끼가 무엇으로 바뀌든지 낚싯대는 영향을 받지 않습니다.

이렇게 보니 개방 폐쇄 원칙(OCP)에서 주변의 변화에 대해 닫혀 있다고 한 점이 유사한 것 같습니다.

 

2. 느낀점 및 최종정리

 

공부하고 나니 유사한 원칙들이 있어 혼란스러워 간단히 정리해보겠습니다. 😮

단일 책임 원칙의 문제점은 하나의 클래스가 여러 역할을 맡고 있었습니다.

이것을 해결하기 위한 방법은 단일 책임 원칙, 인터페이스 분리 원칙 두 가지였죠. 👍

단일 책임 원칙(SRP)은 클래스를 쪼개서 여러 개로 분리해버렸습니다. ✨

반면 인터페이스 분리 원칙(ISP)은 하나의 클래스로부터 역할에 따라 메서드를 '제한'했어요. 👌

개방 폐쇄 원칙도 있었습니다. 바다낚시를 하다 민물낚시를 하려면 낚싯대를 매 번 바꿔야 했었죠.

개방 폐쇄 원칙(OCP)를 적용하지 않으면 낚시꾼인 저는 낚싯대에 영향을 받았습니다.

이것을 자기 자신에게는 확장하고, 주변의 변화에 대해서는 폐쇄함으로써 해결했습니다.

이건 정말 의존 역전 원칙(DIP)과 비슷하다는 느낌이 들지 않나요?

의존 역전 원칙의 정의가 자신보다 변하기 쉬운 것에 의존하지 말라는 것이었으니까요. 😊

낚시꾼의 관점에선 계속 변하는 낚싯대에 의존하고 있었던 것입니다.

OCP 예제에서는 상속을, DIP 예제에서는 인터페이스를 사용했기 때문에 더 비슷하게 느껴졌던 것 같습니다.

그러고보니 리스코프 치환 원칙(LSP)도 있었네요.

리스코프 치환 원칙은 상속과 관련이 깊었었습니다.

하위 타입이 상위 타입을 대체할 수 있어야한다는 원칙이었죠.

러시안블루고양이이면서, 자기 자신만의 속성, 행위를 구현한 것처럼요.

이상으로 객체 지향 설계 5원칙 - SOLID에 대해 정리해 보았습니다. 🙌

 

참고 - 김종민 저, 스프링 입문을 위한 자바 객체 지향의 원리와 이해