Composite 패턴은 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 하는 패턴입니다.
이미지출처 : https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%8F%AC%EC%A7%80%ED%8A%B8_%ED%8C%A8%ED%84%B4
여러분은 아래 예시 요구사항을 만족하는 코드를 작성해야 합니다.
현재 강아지멤버십
, 고양이멤버십
이렇게 2가지 멤버십이 존재하는 상황입니다.
각각의 멤버십들은 적립액과 적립율이 존재합니다.
두 가지 멤버십은 동시에 적용할 수 없으며, 가장 높은 적립율을 가진 멤버십을 적용합니다.
public class Membership {
double dogMembershipSaveRate;
long dogMembershipSaveAmount;
double catMembershipSaveRate;
long catMembershipSaveAmount;
}
이런 상황에서 우리는 토끼멤버십
을 추가해야합니다.
가장 간단한 방법은 기존 멤버십 코드에 토끼멤버십 관련 필드들을 추가하는 방법입니다.
public class Membership {
double dogMembershipSaveRate;
long dogMembershipSaveAmount;
double catMembershipSaveRate;
long catMembershipSaveAmount;
double rabbitMembershipSaveRate;
long rabbitMembershipSaveAmount;
}
하지만, 이 방법은 확장에 유연하지 않습니다.
첫 번째로, 토끼멤버십
이 아닌 기린멤버십
, 햄스터멤버십
이 추가된다면 각 멤버십의 적립액, 적립율에 대한 필드가 한 클래스 내에서 계속해서 늘어나야 하죠.
두 번째로, 멤버십 개수가 늘어날수록 여러 멤버십을 동시에 적용할 수 없기 때문에 어떤 멤버십을 적용해야하는지 계산하기 더더욱 복잡해집니다.
현재는 단순히 가장 높은 적립율을 가진 멤버십을 적용하지만, 추후에 적립율과 적립액 모두를 보며 계산해야 할 수도 있기 때문입니다.
이런 상황은 Composite 디자인 패턴을 활용해 좀 더 유연하게 코드를 작성할 수 있습니다.
public interface MembershipType {
double getSaveRate();
long getSaveAmount();
}
public class DogMembership implements Membership {
private final double saveRate;
private final long saveAmount;
@Override
public double getSaveRate() { return saveRate; }
@Override
public long getSaveAmount() { return saveAmount; }
}
public class CatMembership implements Membership {
private final double saveRate;
private final long saveAmount;
@Override
public double getSaveRate() { return saveRate; }
@Override
public long getSaveAmount() { return saveAmount; }
}
public class RabbitMembership implements Membership {
private final double saveRate;
private final long saveAmount;
@Override
public double getSaveRate() { return saveRate; }
@Override
public long getSaveAmount() { return saveAmount; }
}
위처럼 MembershipType 인터페이스(Component)를 만들고 각각의 멤버십들을 Leaf 클래스로 만들 수 있습니다.
그런 후 Composite 클래스를 아래와 같이 만들 수 있습니다.
public class CompositeMembership implements MembershipType {
private List<MembershipType> memberships;
...
@Override
public double getSaveRate() {
var maxSaveRateMembership = memberships.stream()
.max(saveRateComparator);
return maxSaveRateMembership.map(MembershipType::getSaveRate)
.orElse(0.0);
}
...
}
이렇게 만든 Composite 클래스를 통해 모든 멤버십이 유효한 적립액, 적립율을 가지고 있는지 확인하는 것도 쉬워집니다.
MembershipType 인터페이스에 hasSaveRate
메서드를 추가하면 각 멤버십(Leaf 클래스)에서 유효한 적립율을 가지고 있는지 검사하는 메서드를 구현해야하고 이를 Composite 클래스에서도 hasSaveRate
메서드를 멤버십들이 담겨있는 types를 순회하며 판단할 수 있습니다.
이처럼 동일한 방식을 가진 개체들과 그 개체들을 처리하기 위해 동일한 코드를 사용하는 경우에 Composite 디자인 패턴을 사용하면 좀 더 유연한 방식으로 풀어낼 수 있습니다.