Spring OCP, DIP와 그리고 IOC

hyun·2024년 2월 18일

Spring

목록 보기
3/6

✏️ 개요

좋은 객체 지향 설계의 5가지 원칙인 SOLID라고 있다.

  • SRP : 단일 책임 원칙(Single Resposibility Principle)
  • OCP : 개방 - 폐쇄 원칙(Open/Close Principle)
  • LSP : 리스코프 치환 원칙(Liskov Substitution Principle)
  • ISP : 인터페이스 분리 원칙 (Interface Segregation Principle)
  • DIP : 의존관계 역전 원칙 (Dependency Inversion Principle)

모두 중요한 것들이지만 이번 포스팅을 통해서는 OCP, DIP를 통해 Spring의 IOC에 대해 설명하려한다.

❓ OCP, DIP를 만족한 것 같은데...


위 그림을 잠시 설명하겠다. MemberServiceMemberRepository라는 인터페이스를 의존하고 있다. 그리고 MemberRepository의 구현체인 MemoryMemberRepository가 있다. 또, OrderService는 앞서 말한 MemberRepositoryDiscountPolicy라는 인터페이스를 의존하고 있다. 그리고 DiscountPolicy의 구현체인 FixDiscountPolicyRateDiscountPolicy가 있다.

MemberService 코드

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

OrderService 코드

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

    @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);
    }
}

private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

위 코드들을 보면 OCP와 DIP를 잘 준수한 것으로 보이지만 사실은 아니다. 변수타입을 구현체가 아닌 인터페이스로 받고있긴 하지만 new FixDiscountPolicy();로 구현체도 의존하고 있는 것으로 보인다(DIP 위반). 이렇게 되면 예를 들어 FixDiscountPolicy가 아닌 RateDiscountPolicy로 할인 정책을 바꾸려면 new FixDiscountPolicy();를 삭제하고 new RateDiscountPolicy();를 넣어줘야 한다(OCP위반). 즉, 정적인 코드에서 클라이언트 코드가 의존하는 구현체를 알 수 있고 다른 구현체를 사용하게 된다면 클라이언트 코드에서 수정이 필요하다. 이런 문제를 해결해보자.

✔️ AppConfig와 IOC

만약 클라이언트 코드를 건드리지 않고 의존하고 있는 추상화의 구현체를 변경하고 싶다면 어떻게 해야할까? 바로 다른 설정자가 그 구현체를 바꿔주면 된다. 위의 코드는 자신의 주요 기능뿐 아니라 의존해야하는 객체까지도 설정하고 있는 책임을 가지고 있다. 그럼 그 책임을 다른 객체에게 나누어 주어보자.

AppConfig

public class AppConfig {

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

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

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

    public static DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }

AppConfig라는 클래스를 만들어 위 서비스 클래스에서 해주었던 의존할 대상 객체의 생성과 주입을 대신해준다. 그러면 AppConfig는 말그대로 프로그램을 구성해주는 역할이기에 역할을 잘 나누어 주었다고 볼 수 있다. 그럼 기존 코드는 어떻게 바꿀 수 있을까?

MemberService

public class MemberServiceImpl implements MemberService{

//    private final MemberRepository memberRepository = new MemoryMemberRepository();

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
       @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

OrderService

public class OrderServiceImpl implements OrderService{

//    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FIxDiscountPolicy();
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    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에서 주입해주는 구현체를 받기만 할 수 있도록 해주면 된다. 이렇게 되면 추상화에만 의존할 수 있고 그 구현체 중 어떤 것을 쓸건지 클라이언트 코드에서는 알필요가 없어진다.

이렇게 IOC(제어의 역전)를 만족하도록 바꿀 수 있다. 즉, 객체가 자기 자신을 제어하는 것이 아닌 AppConfig와 같은 설정자에게 제어권을 위임하게 할 수 있다. 이렇게 되면 더욱 객체지향스럽게 개발이 가능하다. 이런 것을 편리하게 도와주는 것이 Spring이다. 다음에는 Spring Container에 대한 포스팅도 해보겠다.

0개의 댓글