Skip to content

기본 : Section 3

영주 edited this page May 6, 2024 · 2 revisions

새로운 할인 정책 추가 설계

DIP : 클래스 의존 관계 확인


실제 의존 관계

실제 의존 관계

  • 역할(DiscountPolicy)뿐만 아니라 구현(RateDiscountPolicy)에도 의존하고 있음 → DIP 위반

<해결>

: 인터페이스에만 의존하도록 코드를 변경해야 함!!

// private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 고정 할인 정책
private DiscountPolicy discountPolicy; // 인터페이스인 DiscountPolicy에만 의존 -> DIP 지킬 수 있음

→ 하지만 실제로 실행하면 Null Pointer Exception 에러 발생 ⇒ 파라미터로 구현 객체를 누군가가 대신 생성해서 주입해줘야 함

OCP : 변경하지 않고 확장 가능한가?


스크린샷 2024-05-06 오후 5 49 09
  • 할인 정책을 FixDiscountPolicy에서 RateDiscountPolicy로변경하려면 OrderServiceImpl 코드를 고쳐야 함

    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 고정 할인 정책
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 정률 할인 정책
  • 현재 코드 : 확장 시, 클라이언트 코드 수정해야 하므로 OCP 위반

관심사의 분리

OrderServiceImpl이 직접 객체를 생성하고 할인 정책을 선택해서 할당함

AppConfig


AppConfig ? 애플리케이션의 실제 동작에 필요한 구현 객체를 생성하고, 생성한 객체 인스턴스의 레퍼런스를 생성자를 통해서 주입(연결) 하는 별도의 설정 클래스

private final MemberRepository memberRepository;

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

public class AppConfig {
  public MemberService memberService() {
    return new MemberServiceImpl(new MemoryMemberRepository());
  }
}
  • AppConfig를 통해 memberService를 부름 (생성자 주입) → MemberRepositoryImpl의 생성자를 통해 객체 생성, MemberRepositoryImpl에는 구현 의존 X , 오로지 인터페이스에만 의존
  • MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지는 알 수 없음
  • MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 AppConfig에서 결정됨

<클래스 다이어그램>
스크린샷 2024-05-06 오후 5 50 52

AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
  • AppConfig를 사용해서 memberService를 호출 및 사용하게 해줌

새로운 구조와 할인 정책 적용

AppConfig : 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리가 가능해짐
스크린샷 2024-05-06 오후 5 51 21

  • 할인 정책을 변경해도 구성 영역인 AppConfig만 수정해도 됨, 사용 영역에는 영향 X

SOLID 원칙의 적용

여기서는 SRP, DIP, OCP 적용

SRP


  • 클라이언트 객체는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있음
  • SRP 단일 책임 원칙을 따르면서 관심사를 분리함
  • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당
  • 클라이언트 객체는 실행하는 책임만 담당

DIP


  • 클라이언트 코드가 DiscountPolicy 추상화 인터페이스에만 의존하도록 코드 변경
  • AppConfigFixDiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계 주입

OCP


  • 애플리케이션을 사용 영역과 구성 영역으로 나눔
  • AppConfig가 의존관계를 FixDiscountPolicy RateDiscountPolicy로변경해서 클라이언트 코드에 주입 -> 클라이언트 코드 :변경 필요 X

IoC, DI, 컨테이너

제어의 역전 IoC (Inversion of Control)


  • 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리
  • AppConfig : 프로그램의 제어 흐름에 대한 권한 가짐 → 구현 객체 : 자신의 로직을 실행하는 역할만 담당함 (흐름 제어권 X)

<프레임워크 vs 라이브러리>

  • 프레임워크 : 내 코드를 제어하고 대신 실행 (JUnit, test framework)
  • 라이브러리 : 코드가 직접 제어의 흐름 담당 (내가 라이브러리를 직접 호출함)

의존관계 주입 DI(Dependency Injection)


의존관계 주입 ? 애플리케이션 실행 시점에서 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것

  • 정적인 클래스 의존 관계 와 실행 시점에 결정되는 동적인 객체 의존 관계 들을 분리해서 생각해야 함
  • 코드에서는 AppConfig가 의존관계를 주입해줌
  • 정적인 클래스 의존 관계
    • import 보고 의존 관계 파악 가능
  • 동적인 객체 인스턴스 의존 관계
    • 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조 보고 의존 관계 파악 가능
    • 실행을 해봐야 알 수 있음

<장점>

  • 클라이언트 코드 변경 없이, 클라이언트가 호출하는 대상의 타입 인스턴스 변경 가능
  • 정적인 클래스 의존 관계 변경 없이, 동적인 객체 인스턴스 의존 관계 쉽게 변경 가능

컨테이너


컨테이너 : AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것

  • IoC 컨테이너, DI 컨테이너, 어셈블러, 오브젝트 팩토리 등으로 불림
    • 현재는 DI 컨테이너라는 용어를 사용함

스프링으로 전환

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
  .
  .
  .
  • @Configuration : AppConfig에 설정 구성
  • @Bean : 스프링 컨테이너에 스프링 빈으로 등록
    → 스프링 컨테이너 : @Configuration이 붙은 AppConfig를 설정 정보로 사용함

// 스프링 컨테이너 : ApplicationContext
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

getBean : memberService 라는 이름의 메서드를 가진 스프링 빈을 찾음