[Spring Boot] 스프링 핵심 원리 이해2 - 객체 지향 원리 적용

토끼는 개발개발·2022년 3월 11일
0

Spring Boot

목록 보기
14/17
post-thumbnail

AppConfig


애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들자.

앞서 우리는 프로젝트 코드가 OCP, DIP를 위반한다는 것을 알았다.
새로운 할인 정책을 적용하려면 '클라이언트 코드'인 주문 서비스 구현체도 함께 변경해야 했다. 인터페이스뿐만 아니라, 구체 클래스도 함께 의존하고 있었던 것이다.

이 문제를 어떻게 해결할 수 있을까?

  • 클라이언트 코드인 OrderServiceImplDiscountPolicy 의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다.
  • 그래서 구체 클래스를 변경할 때 클라이언트 코드도 함께 변경해야 한다.
  • DIP 위반 추상에만 의존하도록 변경(인터페이스에만 의존)
  • DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.

인터페이스에만 의존하도록 OrderServiceImpl코드를 변경해보자.


public class OrderServiceImpl implements OrderService {
	//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
	private DiscountPolicy discountPolicy;
}

해당 코드로 변경하여 실제 실행을 해보면 NPE(null pointer exception)가 발생한다.
이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입해주어야 한다.

바로 AppConfig이다.



▶ AppConfig


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

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

- MemoryMemberRepository
- OrderServiceImpl
- FixDiscountPolicy

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

- MemberServiceImpl ▶ MemoryMemberRepository
- OrderServiceImpl ▶ MemoryMemberRepository , FixDiscountPolicy


AppConfig를 통해서 생성자를 통해 주입할 것이므로, MemberServiceImplOrderserviceImpl에 생성자를 만들어 주어야 한다.



MemberServiceImpl - 생성자 주입


public class MemberServiceImpl implements MemberService {

	private final MemberRepository memberRepository;

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

OrderServiceImpl도 마찬가지로 생성자를 만들어 주어야 한다.


OrderServiceImpl - 생성자 주입


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

설계 변경으로 이제 더이상 MemberServiceImplMemoryMemberRepository를 의존하지 않고, OrderServiceImplFixDiscountPolicy를 의존하지 않는다.
단지, 인터페이스만 의존할 뿐이다.

MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다. 예를 들어, MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서 결정된다. MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.



▶ 클래스 다이어그램

  • 객체의 생성과 연결은 AppConfig 가 담당한다.
  • DIP 완성: MemberServiceImpl 은 MemberRepository 인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.
  • 관심사의 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.


▶ 회원 객체 인스턴스 다이어그램

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

AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리됐다. 할인 정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 되고, 사용 영역은 변경할 필요가 없어진 것이다. 물론 클라이언트 코드인 주문 서비스 코드도 변경하지 않는다.



▶ 구성의 분리

AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다. 구성의 분리를 통해 사용영역의 코드는 전혀 손 댈 필요가 없다.

할인 정책을 FixDiscountPolicy에서 RateDiscountPolicy로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.

AppConfig에서 FixDiscountPolicy를 RateDiscountPolicy로 변경했다.

20000원의 10%인 2000원이 할인된 결과가 나왔다.

즉, 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppConfig만 변경하면 되는 것이다. OrderServiceImpl 를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없다.

프로젝트는 이제 OCP, DIP를 준수한다.




Ref.
스프링 핵심원리(김영한)

profile
하이 이것은 나의 깨지고 부서지는 샏 스토리입니다

0개의 댓글