가입/주문/할인 예제 및 객체 지향 원리

Sangkyeong Lee·2021년 5월 19일

인프런 강의 < 스프링 핵심 원리 - 기본편 > 정리


비즈니스 요구사항과 설계

회원 도메인 설계

  • 기획자들까지 모두가 볼 수 있는 다이어그램

  • 개발자가 구체화하여 표현한 다이어그램

  • 서버가 실제로 사용하는 유효한 인스턴스 관계를 나타낸 다이어그램
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
}
  • MemberServiceImpl이 MemberRepository(추상화)에도 의존하고, MemoryMemberRepository(구체화)에도 의존하므로 문제가 발생

주문과 할인 도메인 설계

  • 역할과 구현 분리


객체 지향 원리 적용

public class FixDiscountPolicy implements DiscountPolicy {

    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return discountFixAmount;
        } else {
            return 0;
        }
    }
}
public class RateDiscountPolicy implements DiscountPolicy {
    private int discountPercent = 10; //10% 할인

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent / 100;
        } else {
            return 0;
        }
    }
}
  • 정액 정책정률 정책으로 변경해도 테스트가 성공적으로 수행됨.

BUT

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); 바꿔야됨
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
  • 위 코드 처럼 OrderServiceImpl 코드를 고쳐야한다.

문제점

  • 역할과 코드의 분리 ok, 다형성 ok
  • OCP, DIP등의 객체지향 설계 원칙을 준수하지 못했다.
  • 코드 변경 때문에 클라이언트 코드에 영향을 준다 - OCP 위반

DIP 개선

public class OrderServiceImpl implements OrderService {
    //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private DiscountPolicy discountPolicy;
}
  • 인터페이스에만 의존하게 한다.
  • 구현체가 존재하지 않아 Null pointer 에러 발생

관심사 분리

AppConfig

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

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

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

  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체생성한다.

  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.

    • MemberServiceImpl -> MemoryMemberRepository
    • OrderServiceImpl -> MemoryMemberRepository , FixDiscountPolicy
  • Impl입장에서는 철저하게 추상화에만 의존한다.

  • Impl입장에서는 생성자로 어떤 객체가 들어올지 알 수 없다.

  • 관심사 분리가 더 명확해졌다.

  • AppConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값을 memberServiceImpl을 생성하면서 생성자로 전달한다.
  • 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection)/의존관계 주입 또는 의존성 주입이라 한다.

AppConfig를 통한 변경

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

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

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

    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}
  • AppConfig를 역할에 따른 구현이 잘 보이도록 리팩터링 -> 역할 세분화

  • 사용 영역과, 구성 영역으로 분리 되었기 때문에 AppConfig 영역을 수정해도 클라이언트 코드에 영향을 주지 않는다.
  • 사용 영역은 바뀌지 않는다.

좋은 객체 지향 설계 5가지 원칙의 적용

  1. SRP
  • 구현 객체를 생성하고 연결하는 책임을 AppConfig가 담당하도록 함
  • 클라이언트 객체는 실행만 담당
  1. DIP
  • 클라이언트 코드가 DiscountPolicy 추상화 인터페이스에만 의존한다.
  • 구체화에 대한 문제는 AppConfig 의존성 주입으로 해결
  1. OCP
  • 다형성, DIP가 잘 적용 되었다.
  • 따라서 FixDiscountPolicyRateDiscountPolicy로 변경해도 클라이언트 코드를 변경할 필요가 없다.

IOC, DI, 컨테이너

IOC

  1. 제어의 역전 IOC(Inversion of Control)
  • 기존의 클라이언트 구현 객체가 스스로 객체를 생성하고 연결하고 실행
  • AppConfig를 이용해 역할과 구현을 분리하고 나면 제어의 흐름은 AppConfig가 가져간다.
  • 제어의 흐름이 내부가 아닌 외부로 넘어가는 것이 IOC라고 부른다.

프레임워크, 라이브러리

  • 프레임워크는 작성한 코드를 제어하고, 대신 실행한다.(JUnit)
  • 라이브러리는 내가 작성한 코드를 내가 직접 담당한다.

DI

  • 정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체 의존 관계를 분리해서 생각해야 한다.

  • 정적 의존 관계는 import 코드만으로 판단이 가능하다.
  • 위 그림에서 OrderServiceImplMemberRepositoryDiscountPolicy에 의존한다.
  • 하지만 무엇이 주입될지는 알 수 없다. - 동적 의존 관계

결론

  • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

컨테이너

  • Appconfig처럼 객체를 생성하고 관리해주는 것을 IoC 컨테이너 또는 DI 컨테이너 라 한다.
  • DI 컨테이너의존 관계 주입에 초점을 맞춘다.

< 자료 출처: 스프링 핵심 원리 - 기본편 >

profile
💻Backend Developer

0개의 댓글