Back-end/Spring

SOLID 적용하여 리팩토링하기

poppy 2022. 1. 10. 20:27
반응형
public class OrderServiceImpl implements OrderService{

    // 구현 클래스에 의존한다 -> DIP 위반
    // 1번
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    // 2번
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    // 기능을 확장하여 변경하려면 클라이언트에 영향을 준다 -> OCP 위반
    // 3번
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

이 코드를 보면 SOLID 원칙을 위반한 것을 볼 수 있다.

먼저 1, 2번 코드는 주문 서비스 클라이언트(OrderServiceImpl)가 인터페이스(MemberRepository)뿐만 아니라, 구현 클래스(MemoryMemberRepository)에 의존하고 있다. -> 따라서 의존관계 역전 원칙(DIP)를 위반하고 있다.

정액 할인에서 정률할인으로 기능을 확장하여 변경하기 위해서 2번 코드에서 3번 코드로 변경하였다. 이렇게 하면 클라이언트의 코드 변경이 일어나므로 클라이언트에게 영향을 준다. -> 따라서 개방 폐쇄 원칙(OCP)를 위반하고 있다.

 

그럼, SOLID 원칙을 지키기 위해서는 어떻게 해야 할까?

클라이언트의 구현 객체를 대신 생성하고 주입하는 역할을 하는 것이 필요하다!!

 

public class AppConfig {

    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

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

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

    public DiscountPolicy discountPolicy(){
        // return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

AppConfig는 실제 동작에 필요한 구현 객체를 생성하고 연결하는 역할을 책임진다.

실제 동작에 필요한 MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, RateDiscountPolicy를 대신 생성해준다. 정액할인에서 정률할인으로 변경하고 싶을 때 AppConfig의 코드만 변경하면 되므로 클라이언트의 코드는 변경되지 않는다.

 

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy; // 인터페이스에만 의존하도록 변경 -> Null Pointer Exception 발생

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

이렇게 생성자를 통해서 AppConfig가 대신 생성한 구현 객체를 주입(연결)해준다. 구현 객체를 생성하는 역할을 분리해주면 클라이언트가 인터페이스에만 의존하게 되므로 DIP를 지킬 수 있고, 기능이 변경될 때 클라이언트의 코드 변경은 일어나지 않아 OCP를 지킬 수 있다.

이것을 의존성 주입(DI)라고 하고 AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 DI 컨테이너라고 한다.

반응형