OCP, DIP 같은 객체 지향 설계 원칙 준수 해보자..!!

무삭이의 개발일지·2023년 3월 4일
0

inflearn Spring 핵심원리 -기본 김영한 강사님 자료 참고

public class OrderServiceImpl implements OrderService

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

역할과 구현을 충실하게 분리 했고, 다형성도 활용하고, 인터페이스와 구현 객체를 분리 했지만, OCP, DIP 같은 객체지향 설계 원칙을 준수하지 못했다.

DIP : 클래스(OrderServiceImpl)는 DiscountPolicy 인터페이스에 의존하면서 DIP를 의존 한 거처럼 보이지만, 클래스 의존관계를 보았을 때, 추상(인터페이스)뿐만 아니라 구체(구현)클래스에도 의존하고 있다.

추상(인터페이스) 의존 : DiscountPolicy
구체(구현)클래스 : FixDiscountPolicy, RateDiscountPolicy

OCP : 변경 하지 않고 확장할 수 있다고 했는데...?
지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

클라이언트인 OrderServiceImpl이 DiscountPolicy 인터페이스 뿐만 아니라 FixDiscountPolicy인 구체 클래스도 함께 의존하고 있다. 실제 코드를 보면 의존하고 있다! DIP 위반


그래서 FixDiscountPolicy를 RateDiscountPolicy로 변경하는 순간 OrderServiceImpl의 소스 코드도 함께 변경해야 한다!! OCP 위반

어떻게 해결할까?

이렇게 만들기 위해서 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입 해주어야 한다.

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 AppConfig {

public MemberService memberService(){
    return new MemberServiceImpl(new MemoryMemberRepository()); //생성자 주입을 활용(injection)
}

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

}

OrderServiceImpl에는 많은 책임이 따랐다. 그래서 AppConfig로 애플리케이션의 실제 동작에 필요한 구현객체를 생성해주고 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.

MemberServiceImpl -> MemoryMemberRepository
OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy

이렇게 하면 OrderServiceImpl은 MemoryMemberRepository, FixDiscountPolicy를 의존 하지 않는다.!!
단지 MemberRepository와 DiscountPolicy만 의존할 뿐이다.
OrderServiceImpl은 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다. AppConfig에서만 결정된다.

이렇게 해서 DIP가 완성됐다. 추상에만 의존 하게 되었다.
AppConfig객체는 MemoryMemberRepository, FixDiscountPolicy 객체를 생성하고 그 참조값을 OrderServiceImpl에 생성하면서 생성자로 전달한다. OrderServiceImpl입장에선 마치 의존관계를 외부에서 주입 해주는 것 같다고 해서 DI(Dependency Injection)의존관계 주입이라 한다.

  • 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존 관계가 연결 되는 것을 의존관계 주입이라 한다.
  • 객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결한다.
  • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

제어의 역전 IoC(Inversion of Control)

기존에는 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성 하고, 연결 하고, 실행 했다. 하지만 AppConfig를 사용한 이후 클라이언트 구현 객체는 자신의 로직만 실행하는 역할을 담당하게 되었다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다. 그 이유로 OrderServiceImpl은 이제 필요한 인터페이스들을 호출하지만 어떤 구현 객체가 실행 될지 모른다.

이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)라고 한다

  • 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다.(JUnit)
  • 반면 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.

IoC컨테이너, DI컨테이너
AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC컨테이너 또는 DI컨테이너라 한다. 또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 한다.

AppConfig로 DI를 했지만, 이제는 스프링 컨테이너를 통해서 사용한다.

ApplicationContext를 스프링 컨테이너라고 한다.
스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정(구성)정보로 사용한다. 그리고 메서드들에 @Bean을 적어준다. 각 메서드들을 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다. 스프링 빈이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. memberService, orderService..!!
스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean()메서드를 사용해서 찾을 수 있다.

profile
No. Try not. Do or Do not. There is no try.

0개의 댓글