강의를 담아 가격을 계산하는 로직에서 각 유형별로 특별한 로직(프로모션)을 부여하고자 한다.
public enum LectureType {
DevOps("DevOps"), DBMS("DBMS"), Lang("Lang"), FW("F/W"), CS("CS");
...
}
다음과 같이 유형은 Enum으로 구현된 상태였고, 각 로직은 모두 "할인을 적용한다"는 측면에서 같은 역할을 가졌으나 개별적인 로직이 필요했다.
| 유형 | 프로모션 |
|---|---|
| DevOps | 모든 강의 10% 할인 |
| DBMS | 강의 마다 5,000원 할인 |
| Lang | 2개 이상 구매 시 가장 가격이 저렴한 강의 하나 무료 |
| F/W | 구매 금액이 90,000원을 넘을 경우 30,000원 할인 |
| CS | 강의 3과목 이상 구매 시 강의 30% 할인 |
SRP(Single Responsibility Principle)는 하나의 객체는 하나의 책임만을 가져야 한다는 의미이다. 이들은 모두 "강의"라는 틀로 묶일 뿐더러 프로모션을 적용하는 상황에서는 열거를 표현하는 Enum에게 그 책임을 지우는 것은 과도할 것이다. 하지만 만약 이들을 관련짓지 않고 다른 클래스로 두기에는 높은 연관성을 지닌다. 이와 관한 문제를 해결하기 위해 다형성의 개념을 적용해 볼 수 있다. 다형성은 하나의 형식에 다양한 구현을 대입할 수 있음을 의미하며 상속 등을 통해 구현이 가능하다. 이 경우에도 프로모션을 적용해 계산한다는 행동의 형식은 같지만 실제적인 구현은 상이한 형태를 지닌다. 이에 상속을 통해 해당 문제를 해결했다.
Lecture에 계산하는 로직을 두고 클래스를 상속받아 각 유형의 강의를 구현했다.
public class Lecture {
int id;
String name;
LectureType type;
int cost;
...
public int calculateCost(List<Lecture> lectures) {
return lectures.stream()
.map(Lecture::getCost)
.reduce(0, Integer::sum);
}
...
}
public class CSLecture extends Lecture {
public CSLecture() {
super();
}
public CSLecture(int id, String name, LectureType type, int cost) {
super(id, name, type, cost);
}
@Override
public int calculateCost(List<Lecture> lectures) {
int price = lectures.stream()
.map(Lecture::getCost)
.reduce(0, Integer::sum);
if (lectures.size() >= 3) {
return (int) (price * 0.7);
}
return price;
}
}
package mission.application.domain.lecture;
import java.util.Comparator;
import java.util.List;
import mission.application.domain.Lecture;
import mission.application.domain.enums.LectureType;
public class LangLecture extends Lecture {
public LangLecture() {
super();
}
public LangLecture(int id, String name, LectureType type, int cost) {
super(id, name, type, cost);
}
@Override
public int calculateCost(List<Lecture> lectures) {
int price = lectures.stream()
.map(Lecture::getCost)
.reduce(0, Integer::sum);
if (lectures.size() >= 2) {
price -= lectures.stream()
.min(Comparator.comparing(Lecture::getCost))
.get()
.getCost();
}
return price;
}
}
...
사실 Enum을 지우지는 않았다. 일단 구현해두고 리팩터링하면서 지우려고 했는데 아직도 리팩터링을 못했다...