Spring에 발 들이기 2

바람찬허파·2023년 8월 30일
0

문제점

이전 포스팅에서 작성했듯, 다형성만으로는 역할과 구현의 구분이 이루어지지 않는다.

public class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository = new MemoryMemberRepository();

MemberRepository 라는 인터페이스의 구현체로 MemoryMemberRepository와 JdbcMemberRepository를 만들었다고 가정하자. 각각 로컬에 저장하는 경우, DB를 사용하는 경우에 기능한다.

다형성에 따라 어떤 저장소를 사용할 지를 바꾸어줄 수 있으며, 다른 구현체로 바꾸더라도 동작에 문제가 생기지 않을 것이다. (같은 인터페이스의 구현체이므로)

앞선 코드의 MemberServiceImpl은 MemberRepository 인터페이스(역할)에 의존함과 동시에, MemoryMemberRespository 구현체 (구현)에 의존한다. 이는 SOLID에 위배되는 형태이다.

SOLID 위배

  • DIP (의존성 역전 원칙) 추상적인 것에 의존해야 하나, 구체적인 것 MemoryMemberRepository 객체에도 의존하였다.
  • OCP (개방폐쇄원칙) 만약, JdbcMemberRepository로 구현체를 바꾸고자 한다면, MemoryMemberRepository의 부분을 모두 변경해야 할 것이다. 변경에는 닫혀있어야 하나, 이를 위반하였다.
  • SRP (단일 책임의 원칙) MemberServiceImpl 클래스는 Service 실행 외에 어떤 MemberRepository 구현체를 사용할지 결정해야 한다. 해당 클래스는 단일 책임을 가지고 있지 않다.

관심사의 분리

MemberServiceImpl 클래스 내의 관심사의 분리가 이루어지지 않고 있다. 역할과 구현의 예시로 본다면, 뮤지컬 로미오의 역할(MemberService)을 맡은 배우(MemberServiceImpl)가 뮤지컬을 함과 동시에, 쥴리엣 역할(MemberRepository)의 배우(MemoryMemberRespotiory)를 결정해야 하는 격이다.

배우가 뮤지컬만 할 수 있도록, 관심사를 분리하는 과정이 필요하다. 배우를 결정하는 것은 기획자의 역할로서, AppConfig가 맡아주어야 한다.

의존성 주입

AppConfig의 코드는 아래와 같다.

public class AppConfig {
    public MemberService memberService(){
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService(){
        return new OrderServiceImpl(new RateDiscountPolicy(), new MemoryMemberRepository());
    }
}

다른 class에서 MemberService 메서드를 호출하면 -> MemberServiceImpl 구현체를 리턴함과 동시에, MemoryMemberRepository 객체를 생성해 파라미터로 보낸다

public class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

MemberServiceImpl에서는 생성자에 따라 MemberRepository의 구현체로 MemoryMemberRepository를 받게 된다.

이 때 MemberServiceImpl의 입장에서는, 외부에서 의존성을 주입해주는 것이기 때문에 의존성 주입(DI)라고 부른다.

앞선 코드와 달라진 점

//기존 코드
private final MemberRepository memberRepository = new MemoryMemberRepository();

//의존성 주입
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
	this.memberRepository = memberRepository;
}

기존 코드에서는 해당 클래스가 직접 MemoryMemberRepository 구현체를 결정해야 했으나 <-> 의존성 주입을 통해, 구현체가 생성될 때 필요한 MemoryMemberRepository를 주입받는다.

SOLID

  • DIP (의존성 역전 원칙) 추상적인 것에 의존해야 하나, 구체적인 것 MemoryMemberRepository 객체에도 의존하였다.

    이제는 MemberRepository 인터페이스에만 의존한다.

  • OCP (개방폐쇄원칙) 만약, JdbcMemberRepository로 구현체를 바꾸고자 한다면, MemoryMemberRepository의 부분을 모두 변경해야 할 것이다. 변경에는 닫혀있어야 하나, 이를 위반하였다.

    만약 MemoryMemberRepository를 JdbcMemberRepository로 바꾼다면, AppConfig의
    public MemberService memberService(){
    return new MemberServiceImpl(new MemoryMemberRepository());
    } 부분만 변경하면 된다. 변경에 닫혀있게 된다.

  • SRP (단일 책임의 원칙) MemberServiceImpl 클래스는 Service 실행 외에 어떤 MemberRepository 구현체를 사용할지 결정해야 한다. 해당 클래스는 단일 책임을 가지고 있지 않다.

    AppConfig가 MemberServiceImpl 클래스에 의존성을 주입하므로, 구현체를 결정할 필요 없이, 실행만 하면 된다!

AppConfig 리팩터링

기존 AppConfig의 코드는 역할이 제대로 보이지 않았다.

public class AppConfig {
    public MemberService memberService(){
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService(){
        return new OrderServiceImpl(new RateDiscountPolicy(), new MemoryMemberRepository());
    }
}
public class AppConfig {
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

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

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

    private static DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }


}

파라미터로 있던 객체 생성 코드를 새로운 메소드로 생성하였다.

  • 구현체를 변경하기 쉽다 > JdbcMemberRepository로 변경하려면, 기존에는 2군데를 다 변경해야 했지만, 수정 후 코드에서는 memberRepository 메소드의 리턴값만 변경하면 된다.
  • 한 눈에 역할, 구현이 보인다. > 수정 후 코드를 보면, MemberService의 역할은 MemberServiceImpl이 구현하며, 이는 memberRepository가 필요하다. memberRepository는 memoryMemberRepository가 구현한다.
    해당 사실들을 한 눈에 파악 가능하다.

이전에 강의를 들었을 땐 DI의 개념이 굉장히 어렴풋이 이해가 갔는데, 다시 강의를 듣고, 오늘 발 들이기 시리즈로 정리하며 비로소 내 것이 된 기분이다. 왜 다형성만으로는 역할과 구현이 완전히 구분될 수 없는지에 대해서!
지금 JAVA 코드로도 충분하다고 생각이 드는데, Spring이 도입됨에 따라 어떤 부분이 변경되고 편리해질지 궁금하다.

0개의 댓글