Programming-[Backend]/Spring

[스프링 기초] 6. 사용영역과 구성영역 나누기로 SRP, OCP, DIP 원칙 실현하기

컴퓨터 탐험가 찰리 2021. 6. 6. 22:39
728x90
반응형

 

1. 변경 코드 적용 : RateCaringPolicy

 

동물원의 정책이 바뀌어서, FixedCaringPolicy에서 RateCaringPolicy로 변경됬다. 다행히도 다형성을 지켜가면서 인터페이스로 역할을 만들어놨기 때문에, 기존 인터페이스를 상속받는 새로운 클래스를 만들기가 수월하다.

RateCaringPolicy
CaringPolicy 역할(interface)을 그대로 상속받기 때문에 작성해놓았던 totalExpense 메서드를 그대로 활용할 수 있다.

 

 

 

 


 

2. SRP, OCP, DIP 위반

 

 

그런데, 바뀐 RateCaringPolicy를 적용하기 위해서 클라이언트와 소통하는 Service 코드를 살펴보면 OCP, DIP 그리고 SRP가 위반되어 있는 것을 알 수 있다. 아래 코드를 보면서 뭐가 잘못된 것인지 알아보자.

 



DIP 위반(Dependency Inversion Principle) : 구현체에 의존하지 말고 추상화에 의존하라.


CreatureRepository와 CaringPolicy는 인터페이스이다. 그러나 인터페이스는 메서드와 입력값인 파라미터, 출력값인 리턴값을 정해줄 뿐이기 때문에, 실제 Service 에서 활용할 수 없다. 따라서 해당 인터페이스를 상속받는 구현체를 new 키워드를 통해서 만들어주었는데, 이것은 결국 추상화에도 의존하고, 구현체에도 의존하게 되어 DIP를 위반하게 된다.

 

 


OCP 위반(Open-Closed Principle) : 변경은 불가하나, 확장은 가능해야 한다.

 

FixedCaringPolicy에서 RateCaringPolicy로 변경하는 부분을 보면, 정책 변경을 위해서 Service의 코드를 변경한 것을 볼 수 있다. OCP의 원칙은 사용영역의 코드를 변경하지 않고 프로그램을 확장가능하게 해야하는 것인데, 이를 위반한 것이다.

 



SRP 위반(Single Responsibility Principle) : 하나의 객체는 하나의 책임만 가져야 한다.


Service 클래스는 비즈니스 로직과 관련된 실행에만 책임을 가져야 하는데, 다른 객체를 생성하고 구성하는 구성의 책임도 갖게 되어 SRP 원칙을 지키지 못하게 되었다.

 

 

 


 

 


 

3. 해결책 : 사용영역과 구성영역을 나누자


DIP와 OCP를 지키기 위해서, 수정해야될 부분은 Service에서 Repository나 Policy등을 선언하고 할당받는 부분임을 알 수 있다. 이 부분을 구성 영역이라고 정의하자. 그리고 이 구성 영역을 다른 클래스로 빼버리자!

 

구성 영역 : AppConfig.class

 

 

 

일단 PlanService에서 문제가 되는 구성영역 부분을 아래 그림과 같이 수정한다.

 

 

 

인터페이스 부분만 남게 되었다. 이제 필요한 구현체 부분을 AppConfig.class에 작성한다.

 

 


PlanService를 호출할 건데, PlanServiceImpl이라는 구현체를 반환하도록 하고, 그것의 생성자 인자로 Repository와 Policy의 구현체들을 넣어준다. 이렇게 되면 어떤 곳에서 Service를 호출했을 때, 반환되는 ServiceImpl 구현체는 CreatureRepositoryAnimals와 RateCaringPolicy 구현체를 갖고 있는 구현체로 반환되는 것이다.

 

 

 



생성자 주입


다만, Service 코드에서 아래와 같이 생성자를 작성해주어야 한다. 생성자를 통해서 구현체들을 주입받는 것생성자 주입이라고 부른다.
-> AppConfig에서 PlanServiceImpl을 반환하는 코드와 잘 비교해보자. PlanServiceImpl은 파라미터로 CreatureRepository, CaringPolicy를 받도록 지정해뒀는데, 이게 AppConfig에서 각각 new CreatureRepositoryAnimals(), new RateCaringPolicy()로써 인자로 들어가게 된다. 그리고 this... 아래 구문들을 통해서 필드값으로 정의한 creatureRepository, caringPolicy에 각 구현체가 할당되어 PlanServiceImpl 구현체가 완성되는 구조이다.

 

 

 

 

 


기존 CreatureServiceImpl 쪽도 변경해주자.

 


변경전

 

 

 


변경후, 차례대로 CreatureServiceImpl, AppConfig

 

 

인터페이스에만 의존하고, 생성자를 작성해서 구현체를 주입받도록 한다.

 

 

 


IntelliJ에서 웬 2 related problems가 있다고 한다. 클릭해서 들어가보면, 테스트 코드에서 문제가 있음을 알 수 있는데, 아래 4장에서 설명한다.

 

 


 

4. 테스트 해보기 : @BeforeEach

 


에러가 난 곳은 Service 테스트 코드였다. 테스트 코드에서도 기존에는 구현체에 의존하고 있었다. 이것을 @BeforeEach 어노테이션을 활용해서 각 테스트마다 테스트 시작 전에 ServiceImpl 구현체를 AppConfig에서 불러와서 처리한다. @BeforeEach 어노테이션은 각 @Test를 실행하기 전에 실행된다.

 

 


main 클래스를 이용해서 테스트했던 부분도 수정해준다.

 

 


 

5. 정리


Service가 의존하던 부분들을 구성영역으로 따로 빼서 SRP, OCP와 DIP를 지킬 수 있게 되었다. 그리고 이것을 각 Service 구현체의 생성자를 통해서 Repository와 Policy의 구현체를 주입받는 형태로 만들어서 프로그램이 작동하도록 해주었다.

 

 


 

6. AppConfig의 역할과 구현 나누기

 

 

설정파일이라고 할 수 있는 AppConfig의 내용을 보면, Service와 Repository, Policy가 혼재되어 있음을 볼 수 있다. 깔끔하게 리팩토링해서 역할과 구현 부분이 명확히 구분되어 보이도록 한다.

 



기존 코드

 

 


new CreatureRepositoryAnimals() 부분에 커서를 위치시키고, [Ctrl + Alt + M] 키를 누르면 refactoring을 할 수 있다. 중복 부분도 한 번에 처리할 수 있다.

메서드들의 순서는 커서를 메서드 위에 위치시킨 후, [Ctrl + Shift + 위아래 방향키] 를 통해 바꿔줄 수 있다.

 

 


변경후 코드


Service, Policy, Repository가 명확히 표현되었다. 이제 만약 CreatureRepositoryAnimals라는 동물원을 CreatureRepositoryPlants 라는 식물원으로 바꾸고 싶다면, 이 파일에서 수정하면 된다. 실행 영역의 코드는 변경 없이 설정 영역의 코드만 바꿈으로써, OCP, DIP 원칙을 지킬 수 있게 되었다!

 

 

 

 

 


 

참조

1. 인프런_스프링 핵심 원리 기본편_김영한 님 강의
www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

728x90
반응형