카테고리 없음

스프링 핵심 원리 기본편 - 스프링 핵심원리 이해 2 객체 지향 원리 적용

우디혜 2021. 5. 1. 23:28

 

사실 객체 지향 5대 원칙들이 다 비슷비슷하고 연관성이 깊어서 정확히 어떤 상황에 어떤 원칙에 위배되는지 혹은 지켜지고 있는지 판단하기가 애매했는데 아주 쉽게 예시를 들어서 잘 설명해주셨다

 

처음 코드는 아래와 같았다

public class OrderServiceImpl implements OrderService {

    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

구체 클래스와 인터페이스를 적절히 사용해서 역할과 구현을 충분히 나눴다.

 

💥 DIP 원칙 위배

하지만 OrderServiceImpl에서는 인터페이스 뿐만 아니라 구체클래스인 FixDiscountPolicy()에 의존하고 있다.

클래스 다이어그램을 만들어보면 아래와 같다.

 

DIP 위배 : OrderServiceImpl 클래스가 구체 클래스인 FixDiscountPolicy에 의존하는 형태

 

💥 OCP 원칙 위배

그리고 FixDiscountPolicy 대신 RateDiscountPolicy로 변경하려는 순간 OrderServiceImpl 코드를 수정해야하는 상황이 발생한다.

기능을 추가했을 때도 기존의 코드 변경을 최소화 해야한다는 OCP 원칙에 위배된다.

public class OrderServiceImpl implements OrderService {

    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

 

OCP 위배 : RateDiscountPolicy로 변경하려면 OrderServiceImpl 코드를 수정해야 함

 

💡Solution : 관심사를 분리하자

 

현재 문제는 OrderServiceImpl 클래스가 의존성 관련 정책을 구체적으로 직접 결정하고 있다는 것이다.

객체 지향적으로 코드를 설계하려면 한 클래스에서는 하나의 책임만을 담당해야 한다.

따라서 OrderServiceImpl에서는 'Order 관련 서비스에 대한 책임'만을 가지게 하기 위해서 '의존성 관련 책임을 다른 클래스에 위임'해야할 필요가 생겼다.

(한 마디로 OrderServiceImpl 클래스가 가진 관심사를 분리해야 한다!)

 

 

일단 OrderServiceImpl는 생성자를 통해서 의존성을 주입받도록 한다.

public class OrderServiceImpl implements OrderService {

    private final DiscountPolicy discountPolicy;
    
    public OrderServiceImpl(DiscountPolicy discountPolicy) {
    	this.discountPolicy = discountPolicy;
    }
    

 

우리가 원하는 이상적인 구조

 

이제 OrderSerivceImpl는 실행에만 집중하면 된다! 의존성에 대해서 생각할 필요가 없다.

DiscountPolicy라는 인터페이스에만 의존한다, 구체 클래스는 몰랑 👉 DIP 원칙 ✅

할인 정책이 변경되더라도, OrderSerivceImpl의 코드는 변경하지 않아도 괜찮다 👉 OCP 원칙 ✅

 

읭 그럼 의존성은 어디서 주입받는걸까?

 

AppConfig라는 클래스를 새로 만들고서 여기에 실제 구현 객체를 생성하고 연결하는 책임을 맡겨보자!

 

구현한 AppConfig 클래스는 다음과 같다

public class AppConfig {

    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    private DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

이렇게 구현 영역(AppConfig)과 사용 영역(OrderServiceImpl)을 분리하게 된다면 의존성이 변경되었을 때도 사용영역인 OrderSerivce 코드에 손댈 필요없이 AppConfig 내부에서 discountPolicy() 메소드 내부만 변경해주면 끄읏!

 

즉, 이렇게 관심사를 분리하게 된다면 소프트웨어 요소를 새롭게 확장하더라도 사용영역의 코드는 수정이 필요 없게 된다! 이지피지

 

 

👍 DIP OCP 한 번 더 짚고 넘어가자 

 

DIP 원칙

상위 모듈, 하위 모듈 모두 추상화에 의존해야 한다.

즉, 변화가 거의 없는 것에 의존해야 한다.

추상적인 인터페이스는 거의 변화가 없는 반면, 구체적인 클래스는 변화할 가능성이 높다.

따라서 의존성이 필요하다면 최대한 인터페이스에 의존하도록 구현하여 유연성을 높여야 한다.

 

OCP 원칙

변경에는 닫혀있고 확장에는 열려있어야 한다.

즉, 기존의 코드를 변경하지 않으면서 기능 추가가 가능해야 한다.

이 또한 다형성을 통해서 원칙을 지킬 수 있다.

 

 

⭐️ 알게된 소소한 팁

 

테스트 클래스 자동 생성

해당 클래스의 테스트 클래스를 자동 생성하고 싶다면

command shift T

 

Assertion static import 

Assertions 사용 시 static import 사용하라 -> 테스트 코드에서 자주 사용하는 코드니까~

(해당하는 코드에서 option + enter 누르면 static import 자동생성 사용할 수 있다.)

import static org.assertj.core.api.Assertions.*;