본문 바로가기
Spring/스프링 핵심 원리 - 기본편

SOLID: 좋은 객체 지향 설계의 5가지 원칙

by hk27 2022. 1. 2.

 

좋은 객체 지향 설계를 하기 위해, 어떻게 코드를 작성하면 될까요?

 

 

안녕하세요. 좋은 객체 지향 설계의 5가지 원칙 SOLID에 대해 알아보겠습니다. 

Spring에서 사용되는 언어인 자바는 대표적인 객체 지향 설계 언어 중 하나인데요. 

Spring 공부의 첫 단계로, 좋은 객체 지향 설계를 위한 원칙 5가지를 알아봅시다. 

 

<클린코드>라는 책으로 유명한 로버트 마틴이 정리한 좋은 객체 지향 설계의 5가지 원칙은 아래와 같습니다. 

SRP: 단일 책임 원칙 (Single Responsibility Principle)

OCP: 개방-폐쇄 원칙 (Open/Closed Principle)

LSP: 리스코프 치환 원칙 (Liskov Substitution Principle)

ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)

DIP: 의존관계 역전 원칙 (Dependency Inversion Principle) 

 

5가지 원칙의 앞 자를 따서 SOLID라고 부르기도 합니다.

각각이 무엇인지 알아봅시다.

 

1. SRP: 단일 책임 원칙 (Single Responsibility Principle)

- 한 클래스는 하나의 책임만 가진다. 

한 클래스에서 UI 변경, 서비스, 저장 등등을 한 번에 처리하는 것 보다, 한 클래스에서 하나의 책임을 하는 것이 바람직합니다. 책임의 크기는 다양할 수 있고, 상황에 따라서 다를 것입니다.

중요한 것은 '변경' 입니다. 변경이 있을 때 파급효과가 적으면 SRP를 잘 지킨 것입니다.

 

2. OCP: 개방-폐쇄 원칙 (Open/Closed Principle)

- 소프트웨어 요소(클래스, 모듈, 함수 등등)확장에는 열려있으나 변경에는 닫혀있어야 한다.

잘 와닿지 않을 수 있는데요, 기능을 변경 또는 확장할 수 있으면서 그 기능을 사용하는 코드는 수정하지 않아야 한다는 것입니다[2].  

자동차를 예로 들어서 생각해보면 좋을 것 같습니다. 

자동차 역할이 인터페이스이고, 자동차 구현이 구현체라고 생각해보면, 운전자는 K3나 아반떼가 전기차인 테슬라 모델3으로 바뀌어도 운전할 수 있습니다. 그런데 운전자 서비스 코드를 짤 때, K3에서 테슬라 모델 3으로 차량을 바꾸려고 하면 운전자 서비스 코드를 수정해야 할 것입니다! 예를 들어서 아래 코드처럼요. 

// Car c = new K3();
Car c = new TeslaModel3();

구현 객체를 변경하기 위해 클라이언트 코드를 변경해야 합니다. OCP가 지켜지지 않는 것인데요. 이를 해결하기 위해서 객체를 생성하고, 연관 관계를 맺어주는 별도의 조립, 설정자가 필요합니다. 이 역할을 DI container가 해주고, 앞으로 배워볼 것입니다.  

 

3. LSP: 리스코프 치환 원칙 (Liskov Substitution Principle)

- 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. 

다형성에서 하위 클래스는 인터페이스의 규약을 다 지켜야 합니다. 예를 들어서 자동차 인터페이스의 엑셀은 앞으로 가라는 기능을 구현해야 할 것이고, 뒤로 간다면 LSP가 위반되는 것이겠죠. 

 

4. ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)

- 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 

자동차 인터페이스 하나보다는, 운전 인터페이스와 정비 인터페이스로 분리하는 것이 낫습니다. 인터페이스가 명확해지기 때문입니다. 

 

5. DIP: 의존관계 역전 원칙 (Dependency Inversion Principle) 

- 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안 된다. 

쉽게 말해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 것입니다. 

"자신보다 변하기 쉬운 것에 의존하지 마라."라는 말로 이해하면 좋을 것 같습니다[3] . 

DIP의 유명한 예시로, 자동차와 스노우타이어 예시가 있습니다. 

자동차와 스노우타이어(눈길 전용 타이어) 사이에 아래 그림과 같은 의존 관계가 있다고 생각해봅시다. 자동차가 스노우타이어에 의존하는 것이죠. 

겨울이 끝나면 스노우 타이어를 일반 타이어로 교체해야 합니다. 그럴 때마다 자동차가 의존하는 대상이 바뀔 것입니다.

이를 해결하기 위해서, 아래 사진처럼 타이어 인터페이스를 의존하면 됩니다.

그러면 타이어가 바뀌어도 자동차 코드는 변하지 않죠.

그런데 이거 2번에서 본 OCP(개방-폐쇄 원칙)와 유사하지 않나요? 

Tire t = new SnowTire();

자동차 코드 내에 위의 코드가 있다면, 이는 인터페이스에 의존하는 것이 맞지만, 구현 클래스도 동시에 의존하고 있는 것입니다. DIP가 지켜지지 않는 것이죠. 아직 OCP, DIP를 지킬 수 없는데, 이는 스프링의 DI(의존관계 주입) 컨테이너가 해결해 줄 것입니다. 어떻게 해결할 수 있는지, 앞으로 자세히 알아봅시다! 

 

인프런  '스프링 핵심 원리 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고자료
[1] 스프링 핵심 원리 - 기본편, 섹션 1. 객체 지향 설계와 스프링 https://www.inflearn.com/course/스프링-핵심-원리-기본편

[2] http://wonwoo.ml/index.php/post/1726

[3] https://server-engineer.tistory.com/228

 

 

댓글