-
[Spring] 좋은 객체 지향 설계의 5가지 원칙(SOLID)Back-end/Spring 2022. 3. 2. 14:48
안녕하세요 이번 포스팅은 그 유명한 SOLID 원칙에 대해서 알아보겠습니다!!
SOLID 원칙
SOLID는 클린 코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리한 것으로 다음과 같습니다.
• SRP: 단일 책임 원칙(single responsibility principle)
• OCP: 개방-폐쇄 원칙 (Open/closed principle)
• LSP: 리스코프 치환 원칙 (Liskov substitution principle)
• ISP: 인터페이스 분리 원칙 (Interface segregation principle)
• DIP: 의존관계 역전 원칙 (Dependency inversion principle)
5가지 원칙 중에서 중요도를 나눠서 비교해 봤을 때 OCP와 DIP가 가장 중요하다고 할 수 있습니다.
1. SRP 단일 책익 원칙
단일 책임 원칙 이란 한 클래스는 하나의 책임만 가져야 한다는 것입니다. 하지만 하나의 책임이라는 것은 모호한 개념입니다. 크기가 클 수도 있고 작을 수도 있기 때문에 문맥과 상황에 따라 그 의미가 달라집니다.
하지만 여기서 중요한 기준은 변경입니다. 프로젝트나 코드의 변경이 생겼을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이라고 할 수 있습니다.
예) UI 변경, 객체의 생성과 사용을 분리
Member와 관련된 비즈니스 로직은 오직 MemberService에서 Order와 관련된 비즈니스 로직은 오직 OrderService에섬나 해야 한다는 것이 단일 책임 원칙의 예라고 할 수 있으며, 스프링의 @Configuration과 @ComponentScan 혹은 @Bean과 같은 어노테이션을 사용 한 클래스에서 의존관계 주입을 할 때 그 클래스는 오직 객체를 생성하고 스프링 빈을 등록하는 책임만을 가집니다.
2. OCP 개방-폐쇄 원칙
개방 폐쇄 원칙의 핵심개념은 '소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다'입니다.
말 자체는 굉장히 이상적인데요 잘 들여다보면 조금 이상합니다. 보통 확장을 하려면 당연히 기존 코드를 변경해야 할 것 같다는 느낌이 들기 때문인데요 이것은 다형성을 활용해서 해결할 수 있습니다.
위의 코드를 보면 MemberService 클라이언트가 구현 클래스를 직접 선택하고 있습니다.
때문에 구현 객체를 변경하려면 클라이언트 코드를 변경해야 하는데, 분명 앞서 강조한 다형성을 활용했지만 OCP 원칙을 지킬 수 없는 것을 확인할 수 있습니다.
이 문제를 해결하기 위해서는 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요합니다.
3. LSP 리스코프 치환 원칙
리스코프 치환 원칙 이란 '프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다'입니다.
이는 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것이고 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 이 인터페이스를 믿고 사용하려면 리스코프 치환 원칙이 필요합니다.
단순히 컴파일에 성공하는 것을 넘어서는 이야기라 할 수 있습니다.
예를 들어서 자동차 역할 인터페이스의 엑셀은 앞으로 가라는 기능이 있어야 하는데 뒤로 가게 구현하면 리스코프 치환 원칙을 위배하게 되는 것입니다. 인터페이스는 느리더라도 앞으로 가야 하는 기능을 갖고 있어야 합니다.
4. ISP 인터페이스 분리 원칙
인터페이스 분리 원칙은 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 것입니다.
예를 들어서 자동차 인터페이스는 운전 인터페이스, 정비 인터페이스로 구분할 수 있고, 사용자 클라이언트는 운전자 클라이언트, 정비사 클라이언트로 분리할 수 있습니다.
이렇게 인터페이스를 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에는 영향을 주지 않습니다. 왜냐하면 운전자 클라이언트는 운전 인터페이스에 의존하고 있기 때문이죠.
5. DIP 의존관계 역전 원칙
의존관계 역전 원칙은 '프로그래머는 추상화에 의존해야지 구체화에 의존하면 안 된다'는 것입니다.
의존성 주입(Dependency Injection)은 이 원칙을 따르는 방법 중 하나입니다.
쉽게 말해서 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻입니다.
OCP를 설명할 때 첨부했던 이미지를 보면 MemberService는 Inteface에도 의존하고 있지만 결국 구현 클래스에도 의존 하고 있기 때문에 이 것은 DIP 위반이라고 할 수 있습니다.
앞서 말했듯이 자동차 역할과 그것을 구현하는 자동차 구현체들이 있는 경우 변경을 용이하게 하기 위해서 자동차 구현체 들에 의존하면 안 되고 자동차 역할 자체에 의존해야 된다는 것입니다.
정리
정리를 하자면, 객체 지향의 핵심은 다형성입니다. 하지만 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없고 구현 객체를 변경할 때 클라이언트 코드를 수정하게 되면서 OCP, DIP를 지킬 수 없게 됩니다.
즉 여기서 우리는 무엇인가가 더 필요합니다.
스프링 프레임워크가 바로 이런 딜레마를 해결해줍니다! 스프링은 DI 컨테이너를 제공하고 의존성 주입(DI)을 통해서 다형성과 OCP, DIP를 모두 실현할 수 있게 지원합니다.
즉 클라이언트 코드 변경 없이 기능을 확장할 수 있습니다.
다음 포스팅은 실제 코딩으로 의존성 주입에 대해 알아보겠습니다!
'Back-end > Spring' 카테고리의 다른 글
[Spring]IoC, DI, 컨테이너 그리고 AppConfig 스프링으로 변환하기, 스프링 빈 조회하기 (0) 2022.03.04 [Spring] DI 컨테이너를 예제 만들기 ( 2 ) (0) 2022.03.04 [Spring] DI 컨테이너 예제 만들기 ( 1 ), JUnit 사용하기 (0) 2022.03.03 [Spring] Spring의 핵심과 객체 지향 프로그래밍 (0) 2022.03.02 [Spring] Spring의 역사에 대해서 알아보자 (1) 2022.03.02