인프런 커뮤니티 질문&답변

BongHo Lee님의 프로필 이미지
BongHo Lee

작성한 질문수

스프링 핵심 원리 - 기본편

새로운 할인 정책 개발

도메인 설계의 가정이 변경되었을 경우

해결된 질문

작성

·

193

9

안녕하세요 영한님. 스프링 강의와 함께 객체지향에 대한 내용도 함께 버무려주셔서 너무 감사드립니다.

질문드리고 싶은 내용은 도메인 설계와 변경 관련된 내용인데요.

처음 '할인 정책' 도메인에 대해서 설계를 할 때 '등급에 따른 할인(정액/정률 etc)'이라는 가정으로 설계를 하고 서비스를 하던 중

이 악덕 기획자놈이 말하길

"회원정보(등급)에 따른 할인 말고 음.. 상품 정보(상품 명이 '깡'으로 끝나는 등)에 따라 할인을 해주는 이벤트를 합시다!" 라는 요구사항이 들어온 경우.

아무래도 현재의 설계에서는 '회원 정보(등급)'을 가정한 설계다 보니 위와 같은 요구사항을 받아들이기 위해서는 '할인 정책'에 대한 가정 자체가 변경되는데

이러한 경우 전체적인 설계를 다시 진행해서 리팩토링을 하시나요? 

(제가 나름대로 변경되는 사항을 고려해서 캡슐화 하고 설계를 진행한다고 했는데 아예 가정 자체가 흔들리는 요구사항을 받은적이 자주 있어서 설계가 변경되는 상황이 자주 발생하더라구요 ㅠ)

 

답변 1

9

안녕하세요. BongHo Lee님, 공식 서포터즈 David입니다.

.

최대한 관심사를 잘 분리해서 설계하는게 변경의 범위를 좁힐 수 있어서 고려하시면 좋을 것 같습니다.

.

예시로 든 요구사항이 원하는 것은 본질적으로는 "할인정책 추가"입니다. 만약 할인정책에 대한 부분들이 설계상 다른 부분들과 잘 분리가 되어있다면 설계 전체를 뒤엎어야 하는 상황은 발생하지 않습니다.

그리고 이런 상황을 맞이하다보면 유연한 설계와 테스트가 얼마나 중요한지 알게됩니다.

.

현재 DiscountPolicy 인터페이스의 구현체로는 FixDiscountPolicy, RateDiscountPolicy가 있습니다.

public interface DiscountPolicy {

int discount (Member member, int price);
}

DiscountPolicy는 Item을 받아들일 수 없는 구조입니다.

Item과 Member는 각 할인정책에 있어서 꼭 필요한 조건들인데요.

다음과 같이 변경하게 되면 기존보다 조금 더 유연하게 할인조건을 받을 수 있습니다.

 

public interface DiscountPolicy<T> {

int discount(T condition, int price);
}

T는 Type을 말합니다.

DiscountPolicy의 구현체를 만들 때 implements DiscountPolicy<Member>로 작성하면 discount 메서드를 구현할 때 discount 메서드의 파라미터로 Member를 받을 수 있게 합니다.

.

이제 인터페이스에서 변경할 것은 다 끝났습니다.

기존 구현체들(Rate, Fix)을 찾아가서 어떤 T(Type)을 쓸지 추가해줍니다.

public class FixDiscountPolicy implements DiscountPolicy<Member> {

@Override
public int discount(Member condition, int price) {
// Member 등급에 따른 할인 로직
return 0;
}
}

그리고 ItemDicountPolicy를 만들고 Item 이름에 따라 할인하는 로직을 구현합니다.

 

public class ItemDiscountPolicy implements DiscountPolicy<Item> {

@Override
public int discount(Item condition, int price) {
// Item 이름에 따른 할인 로직
return 0;
}
}

이러면 다른 설계를 변경할 것도 없이 인터페이스를 리팩토링하고 구현체를 추가하는 선에서 할인정책을 추가할 수 있습니다.

.
감사합니다.

BongHo Lee님의 프로필 이미지
BongHo Lee
질문자

직접 코드 예시까지 작성해주시면서 자세하게 답변해주셔서 정말 감사합니다.

예시로 든 요구사항을 받아들이기 위해 할인정책에 필요한 정보를 어떻게 추상화 시키고 이 추상화를 어떻게 도메인 관계들 사이에 참여시킬지 고민했는데 간단하게 제네릭으로 해결할 수 있었군요..

이번 강의로 스프링 전반 뿐만이 아니라 정말 많이 배우는 것 같습니다. 감사합니다.

와 저도 이게 궁금했는데 감사합니다, 큰거 배웠습니다!!

설명 감사드립니다!

만약에 할인 정책이 회원등급 + 특정 단어가 포함된 조건일 경우, 일반적으로 저렇게 늘려가는게 맞을까요

public interface DiscountPolicy<T, T> {
public class SpecialDiscountPolicy implements DiscountPolicy<Member, Item> {

 

추가로 회원등급, 고정비, 정률, 회원등급+특정 단어 이렇게 케이스가 많을 경우 인터페이스는 어떤식으로 설계가 되어야하는지요? 

인터페이스에 제네릭을 최대한 많이 넣어서 구현체별로 매개변수 필요한거는 넣고 불필요한거는 빈값 넣어주고 이런 방법이 될까요?

hungryo님께서 말씀하신 방식은 제네릭 사용 의도와 맞지 않는 것 같습니다.

말씀하신대로 요구사항으로 할인정책이 늘어났을 때 저라면 할인정책에 필요한 정보 가지는 클래스(회원등급, 아이템 이름 등)를 만들어 사용할 것 같습니다.

이전 요구사항까지는 제네릭으로 각 클래스(member, item)만으로도 요구사항을 만족시킬 수 있었지만 이제는 그렇지 않기 때문입니다. 할인정책에 필요한 정보가 한 객체의 역할(member의 회원등급, item의 이름)을 넘어섰기 때문입니다. 따라서 그에 맞는 새로운 객체를 도출할 것 같습니다.

만약 할인정책의 정보를 가지고 있는 객체가 도출된다면 다시 제네릭은 쓸 필요가 없어집니다.

대신 member, item 등으로부터 필요한 정보를 전달받아 할인정책에 필요한 정보를 가지고 있는 객체를 만드는 팩토리 클래스가 필요합니다.

DiscountPolicy 인터페이스 명세는 discount(DiscountProperty discountProperty, int price);로 바뀔 것입니다.

 

DiscountProperty discountProperty = DiscountPropertyFactory.create(member, item);

 

FixedDiscountPolicy

  @Override

  discount(DiscountProperty discountProperty, int price) {

    // 정률 할인에 따른 계산

  }

오 명쾌한 설명 감사드립니다!

팩토리는 이와 같은 구조가 될까요?

맞는지는 모르겠지만... 맞다면 저 if/else는 피할 수 없는 운명이겠죠?

이 부분부터는 hungryo님께서 고민해보시면 좋을 것 같습니다:)

네. 감사합니다^^

BongHo Lee님의 프로필 이미지
BongHo Lee

작성한 질문수

질문하기