마일리지를 계산하는 정책을 아래와 같이 코드로 구현한다고 하자.
public interface MileagePolicy {
int calculateMileage(Member member, int price);
}
public class FixMileagePolicy implements MileagePolicy {
private int mileageFixAmount = 300; // 300원 고정 마일리지
@Override
public int calculateMileage(Member member, int price) {
if(member.getGrade() == Grade.VIP) {
return mileageFixAmount;
}
return 0;
}
}
이 정책을 사용하는 OrderService의 구현체는 아래와 같이 작성할 수 있다.
public interface OrderService {
Order order(Member member, String itemName, int itemPrice);
}
public class OrderServiceImpl implements OrderService{
private final MileagePolicy mileagePolicy = new FixMileagePolicy();
@Override
public Order order(Member member, String itemName, int itemPrice) {
int mileagePrice = mileagePolicy.calculateMileage(member, itemPrice);
...
}
}
이 코드의 문제점은 MileagePolicy의 특정 구현체인 FixMileagePolicy에 의존하고 있다는 점이다.
따라서 OrderService는 Order를 생성하는 책임과 더불어, MileagePolicy에 어떤 구현체가 적용될지를 결정하는 보조 책임을 함께 지고 있다.
MileagePolicy를 사용하는 클래스가 모두 이러한 보조 책임을 갖게 된다.
해당 클래스들은 모두 아래와 같은 코드를 갖게 되어, 코드가 중복된다.
MileagePolicy mileagePolicy = new FixMileagePolicy();
이럴 경우 문제가 있다.
모든 클래스에서 SRP를 위반하게 된다는 원칙적인 문제와 더불어, 실질적인 문제도 있다.
이는 프로그램에 적용되는 MileagePolicy정책을 바꿀 경우에 발생한다.
public class RateMileagePolicy implements MileagePolicy {
private int mileageRate = 10;
@Override
public int calculateMileage(Member member, int price) {
if(member.getGrade() == Grade.VIP) {
return price * discountRate / 100;
}
return 0;
}
}
위와 같이 할인 정책을 정률적으로 계산하는 것으로 변경한다고 하자.
이럴 경우 아래와 같이 MileagePolicy를 사용하는 모든 부분의 코드를
MileagePolicy mileagePolicy = new FixMileagePolicy();
다음과 같이 변경해야 한다.
MileagePolicy mileagePolicy = new RateDiscountPolicy();
어느 한 부분의 변경이라도 누락되는 순간, 프로그램에 정책적인 모순이 발생한다.
코드의 변경을 줄이는 관점에서도, 단일 책임 원칙을 지키는 관점에서도 의존관계를 설정하는 역할을 담당하는 코드를 따로 작성하는 것이 적절하다.
public class AppConfig {
public MeleagePolicy mileagePolicy(){
return new FixMileagePolicy();
}
public OrderService orderService() {
return new OrderServiceImpl(mileagePolicy());
}
}
이렇게 작성할 경우 OrderServiceImpl에는 생성자를 통해서 구현체가 주입됨을 기술해주기만 하면 된다.
public class OrderServiceImpl implements OrderService{
private final MileagePolicy mileagePolicy;
public OrderServiceImpl(MileagePolicy mileagePolicy) {
this.mileagePolicy = mileagePolicy; // 생성자 주입
}
@Override
public Order order(Member member, String itemName, int itemPrice) {
int mileagePrice = mileagePolicy.calculateMileage(member, itemPrice);
...
}
}
이 때 Application에서 OrderServiceImpl을 사용하는 코드는 아래와 같이 작성할 수 있다.
public class Application {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
OrderService orderService = appConfig.orderService();
Member member = new Member("memberA", Status.VIP);
orderService.order(member, "apple", 4000);
...
}
}
설정 파일인 AppCofig 내부를 제외하면 구현체를 인터페이스에 넣어주는 코드는 필요 없어진다.
의존관계 주입이라는 보조 책임은 AppConfig가 독립적으로 지게 된다.