Blog

[SpringCore] 객체 지향 원리 적용 - 역할, 구현의 분리를 위한 AppConfig의 추가, 제어의 역전(IoC), 의존관계 주입(DI)

Category
Author
citeFred
citeFred
Tags
PinOnMain
1 more property
AppConfig를 통해 DIP, SRP, OCP를 준수하여 분리하기
Table of Content

기존 코드의 문제

주문과 할인을 작성한 OrderService, DiscountPolicy 처럼 서비스 계층이 있다. 아래 작성한 기존 코드는 각 레이어에서 직접 주입받는 객체를 생성(new)하는 문제가 있다.
할인 정책을 변경하려고 코드를 변경하고 있지만 서비스 계층(클라이언트)에서도 추상 클래스만이 아닌 구현 클래스(하위)에도 의존하고 있다.(= 직접 해당 계층에서 코드를 수정하고 있다.) 이는 DIP(추상또는부모클래스 의존)이 위반되고 있다. 또한 기능을 확장하고자 클라이언트 코드(서비스 계층) 코드에 영향을 준다. 이는 OCP(확장개방 수정폐쇄) 위반이다.
package hello.core.order; import hello.core.discount.DiscountPolicy; import hello.core.discount.RateDiscountPolicy; import hello.core.member.Member; import hello.core.member.MemberRepository; import hello.core.member.MemoryMemberRepository; public class OrderServiceImpl implements OrderService{ private final MemberRepository memberRepository = new MemoryMemberRepository(); //private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 기존 고정 할인 정책 private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 변경하고자하는 비율 할인 정책 @Override public Order createOrder(Long memberId, String itemName, int itemPrice) { Member member = memberRepository.findById(memberId); int discountPrice = discountPolicy.discount(member, itemPrice); return new Order(memberId, itemName, itemPrice, discountPrice); } }
Java
복사

변경된 서비스 소스코드

위 코드와 같은 문제점을 해결하기 위해서는 우선 해당 서비스 계층은 추상 클래스만 의존하도록 변경하면 DIP 위반을 막을 수 있다.
OrderServiceImpl(주문 구현 서비스 계층)은 다음과 같이 생성자 주입을 통해서 주입을 원하는 추상 클래스의 객체(구현체)를 주입 받도록 수정한다.
해당 추상 클래스의 구현체는 어디서 생성하고 주입해야 하는가?
package hello.core.order; import hello.core.discount.DiscountPolicy; import hello.core.member.Member; import hello.core.member.MemberRepository; 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; } @Override public Order createOrder(Long memberId, String itemName, int itemPrice) { Member member = memberRepository.findById(memberId); int discountPrice = discountPolicy.discount(member, itemPrice); return new Order(memberId, itemName, itemPrice, discountPrice); } }
Java
복사

앱 구성 영역의 AppConfig.java로 관리

이를 위해서 AppConfig 처럼 구현체 생성과 주입만을 다루는 구성 영역의 역할만 가진 클래스를 생성해준다.
이제 DiscountPolicy가 변경된다면, 또한 Repository의 저장소가 다른 DB로 변경된다하더라도 AppConfig에서만 객체를 변경해주면 된다. 이를 통해 할인 정책을 변경하는 책임은 주문 계층이 다루는게 아니라 그 구성을 변경하는 영역인 AppConfig가 다루게 된다. 이는 확장에 열려있지만 사용 영역(서비스 계층 코드, 클라이언트)에 폐쇄되어 있는 OCP 원칙을 따를 수 있게 된다.
또한, 각 서비스는 비지니스 로직만 관리, 구성 영역은 AppConfig가 관리하는 것 처럼 각 부분의 책임만 가지는 SRP(단일 책임 원칙)를 준수 할 수 있다.
package hello.core; import hello.core.discount.DiscountPolicy; import hello.core.discount.RateDiscountPolicy; import hello.core.member.MemberRepository; import hello.core.member.MemberService; import hello.core.member.MemberServiceImpl; import hello.core.member.MemoryMemberRepository; import hello.core.order.OrderService; import hello.core.order.OrderServiceImpl; 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에서만 다뤄도 된다. } }
Java
복사
더하여 AppConfig 코드 자체만으로도 각 계층이 어떤 객체를 사용하는지, 전체적인 구조를 파악 할 수 있다.

제어의 역전(IoC, Inversion of Control)

과거 코드는 서비스 계층이 어떤 저장소를 사용 할 지, 할인 정책을 사용 할 지 직접 객체를 생성하며(new) 결정했었다. 이는 자연스러운 흐름으로 보여질 수 있다. 하지만 객체지향 원칙들을 준수하고자 현재 AppConfig를 추가하게 되었다.
AppConfig 작성 이후 구현 객체는 각자 자신의 로직만 실행하는 것으로 변경 되었다.
AppConfig가 프로그램의 전반적인 제어 흐름을 가져오게 되었다. 이렇게 실제 프로그램 제어 흐름을 로직이 직접 다루는 것이 아니라 외부(AppConfig와 같이)에서 관리하는 것을 제어의 역전(IoC)라 한다.

프레임워크 vs 라이브러리

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

의존관계 주입(DI, Dpendency Injection)

위 OrderServiceImpl은 DiscountPolicy 인터페이스(상위,추상 클래스)에 의존하도록 변경되어 있다. 실제로는 어떤 구현체(고정할인or비율할인)가 사용 될지는 OrderServiceImpl 본인은 모른다.
의존 관계는 정적인 클래스 의존관계 / 실행 시점에 결정되는 동적인 객체 의존 관계(인스턴스) 를 분리해서 생각.
정적인 클래스 의존 관계
클래스가 사용하는 import 코드로 볼 수 있다.
ex) 아래처럼 DiscountPolicy, Member, MemberRepository 처럼 정적인 클래스를 의존하고 있다.
package hello.core.order; import hello.core.discount.DiscountPolicy; import hello.core.member.Member; import hello.core.member.MemberRepository; public class OrderServiceImpl implements OrderService{ private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; ...
Java
복사
하지만 분기된 실제 구현체가 어떤것이 사용되는지 알기 어렵다.
동적인 실행 시점에 결정되는 의존 관계(인스턴스)
런타임(실행 환경)에서 실제 사용되는 구현 객체가 어떤것들이 사용할 지 연결하는 것 의존 관계 주입
객체 인스턴스를 생성하고 그 참조값을 전달하여 연결한다.
이를 통해 클래스 의존관계를 변경하지 않고도 동적으로 클라이언트가 호출하는 객체 인스턴스 의존관계를 변경 할 수 있다.
package hello.core; ... 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 RateDiscountPolicy(); } }
Java
복사
이처럼 DI를 해주는 AppConfig가 하는 역할을 IoC 컨테이너, DI 컨테이너이라고 한다. 이런 구성 조립을 해주는 것을 어셈블러, 오브젝트 팩토리라고 하기도 한다.
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio