java.util.Stack
)InstrumentedHashMap
)public class Properties {
private HashTable<String, String> properties = new HashTable<>();
public String setProperty(String key, String value) {
return properties.put(key, value);
}
public String getProperty(String key) {
return properties.get(key);
}
}
public class Stack<E> {
private Vector<E> elements = new Vector<>();
public E push(E item) {
elements.addElement(item);
return item;
}
public E pop() {
if (elements.isEmpty()) {
throw enw EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
}
Properties
과 Stack
클래스를 상속에서 합성으로 변경했다.
Properties
와 Stack
클래스의 인터페이스를 오염시키지 않는다.public class InstrumentedSet<E> {
private int addCount = 0;
private final Set<E> set;
public InstrumentedSet(Set<E> set) {
this.set = set;
}
public boolean add(E e) {
addCount++;
return set.add(e);
}
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return set.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
Properties
와 Stack
과 달리 InstrumentedHashSet
은 HashSet
의 모든 인터페이스를 그대로 제공해야 한다.// ...
@Override
public boolean remove(Object o) { return set.remote(o); }
@Override
public void clear() { set.clear(); }
// ...
public class PersonalPlaylist {
private Playlist playlist = new Playlist();
public void append(Song song) {
playlist.append(song);
}
public void remote(Song song) {
playlist.getTracks().remote(song);
playlist.getSingers().remote(song.getSinger());
}
}
Phone
RegularPhone
(일반 요금제)NightlyDiscountPhone
(심야 요금제)Phone
에 새로 메서드를 추가해야 한다.public abstract class Phone {
private List<Call> calls = new ArrayList<>();
public Money calculateFee() {
Money result = Money.ZERO;
for(Call call : calls) {
result = result.plus(calculateCallFee(call));
}
return afterCalculated(result);
}
protected abstract Money calculateCallFee(Call call);
protected Money afterCalculated(Money fee) {
return fee;
}
}
RegularPhone
과 NightlyDiscountPhone
이 일일이 afterCalculated
를 오버라이드 하지 않게 하기 위해 기본 구현을 제공한 것public class TaxableRegularPhone extends RegularPhone {
private double taxRate;
public TaxableRegularPhone(Money amount, Duration seconds, double taxRate) {
super(amount, seconds);
this.taxRate = taxRate;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.plus(fee.times(taxRate));
}
}
public class TaxableNightlyDiscountPhone extends NightlyDiscountPhone {
private double taxRate;
public TaxableNightlyDiscountPhone(Money nightlyAmount, Money regularAmount, Duration seconds, double taxRate) {
super(nightlyAmount, regularAmount, seconds);
this.taxRate = taxRate;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.plus(fee.times(taxRate));
}
}
TaxableRegularPhone
: 일반 요금제에 세금 적용TaxableNightlyDiscountPhone
: 심야 요금제에 세금 적용RageDiscountableRegularPhone
RateDiscountableNightlyDiscountPhone
public class RateDiscountableRegularPhone extends RegularPhone {
private Money discountAmount;
public RateDiscountableRegularPhone(Money amount, Duration seconds, Money discountAmount) {
super(amount, seconds);
this.discountAmount = discountAmount;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.minus(discountAmount);
}
}
public class RateDiscountableNightlyDiscountPhone extends NightlyDiscountPhone {
private Money discountAmount;
public RateDiscountableNightlyDiscountPhone(Money nightlyAmount,
Money regularAmount, Duration seconds, Money discountAmount) {
super(nightlyAmount, regularAmount, seconds);
this.discountAmount = discountAmount;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.minus(discountAmount);
}
}
TaxableAndRateDiscountableRegularPhone
을 추가해야 한다.RateDiscountableAndTaxableRegularPhone
을 추가해야 한다.// 기본 정책과 부가 정책을 포괄하는 인터페이스
public interface RatePolicy {
Money calculateFee(Phone phone);
}
// 기번 정책 추상 클래스
public abstract class BasicRatePolicy implements RatePolicy {
@Override
public Money calculateFee(Phone phone) {
Money result = Money.ZERO;
for(Call call : phone.getCalls()) {
result.plus(calculateCallFee(call));
}
return result;
}
protected abstract Money calculateCallFee(Call call);
}
// 일반 요금제
public class RegularPolicy extends BasicRatePolicy { ... }
// 심야 할인 요금제
public class NightlyDiscountPolicy extends BasicRatePolicy { ... }
Phone
을 기본 정책을 이용해 요금을 계산하도록 수정할 수 있다.
RatePolicy
를 의존하는 부분이 바로 합성이다.public class Phone {
private RatePolicy ratePolicy;
private List<Call> calls = new ArrayList<>();
public Phone(RatePolicy ratePolicy) {
this.ratePolicy = ratePolicy;
}
public List<Call> getCalls() {
return Collections.unmodifiableList(calls);
}
public Money calculateFee() {
return ratePolicy.calculateFee(this);
}
}
Phone
입장에선 자신이 메시지를 전달하는 대상이 기본 정책인지, 부가 정책인지 몰라야 한다.RatePolicy
는 또 다른 RatePolicy
를 합성할 수 있어야 한다.// 부가 정책
public abstract class AdditionalRatePolicy implements RatePolicy {
private RatePolicy next;
public AdditionalRatePolicy(RatePolicy next) {
this.next = next;
}
@Override
public Money calculateFee(Phone phone) {
Money fee = next.calculateFee(phone);
return afterCalculated(fee) ;
}
abstract protected Money afterCalculated(Money fee);
}
// 세금 정책
public class TaxablePolicy extends AdditionalRatePolicy {
private double taxRatio;
public TaxablePolicy(double taxRatio, RatePolicy next) {
super(next);
this.taxRatio = taxRatio;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.plus(fee.times(taxRatio));
}
}
// 기본 할인 정책
public class RateDiscountablePolicy extends AdditionalRatePolicy {
private Money discountAmount;
public RateDiscountablePolicy(Money discountAmount, RatePolicy next) {
super(next);
this.discountAmount = discountAmount;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.minus(discountAmount);
}
}
// 일반 요금제에 세금 정책 조합
Phone phone = new Phone(
new TaxablePolicy(0.05, new RegularPolicy(...));
// 일반 요금제에 기본 요금 할인 정책을 조합한 결과에 세금 정책 조합
Phone phone = new Phone(
new TaxablePolicy(0.05,
new RateDiscountablePolicy(Money.wons(1000),
new RegularPolicy(...)));
// 심야 요금 할인제에 기본 요금 할인 정책을 조합한 결과에 세금 정책 조합
Phone phone = new Phone(
new TaxablePolicy(0.05,
new RateDiscountablePolicy(Money.wons(1000),
new NightlyDiscountPolicy(...)));
TaxablePolicy
만 변경하면 된다.abstract class BasicRatePolicy { // 기본 정책
def calculateFee(phone: Phone): Money = phone.calls.map(calculateCallFee(_)).reduce(_ + _)
protected def calculateCallFee(call: Call): Money;
}
// 표준 요금제
class RegularPolicy(val amount: Money, val seconds: Duration) extends BasicRatePolicy {
override protected def calculateCallFee(call: Call): Money = ...
}
// 심야 할인 요금제
class NightlyDiscountPolicy(
val nightlyAmount: Money,
val regularAmount: Money,
val seconds: Duration) extends BasicRatePolicy {
override protected def calculateCallFee(call: Call): Money = ...
}
object NightltDiscountPolicy {
val LateNightHour: Integer = 22
}
// 세금 정책 트레이트
trait TaxablePolicy extends BasicRatePolicy {
val taxRate: Double
override def calculateFee(phone: Phone): Money = {
val fee = super.calculateFee(phone)
return fee + fee * taxRate
}
}
TaxablePolicy
트레이트가 BasicRatePolicy
를 확장한다.
BasicRatePolicy
나 BasicRatePolicy
자손에 해당하는 경우에만 믹스인될 수 있다는 것을 의미TaxablePolicy
는 RegularPolicy
나 NightlyDiscountPolicy
, 그리고 후에 추가될 BasicRatePolicy의 자식에게만 믹스인될 수 있다.super
호출로 실행되는 메서드 또한 컴파일타임이 아닌 트레이트가 실제로 믹스인되는 시점에 결정된다.extends
와 with
키워드로 믹스인할 수 있다.
extends
를 이용해 상속with
로 믹스인class TaxableRegularPolicy(
amount: Money,
seconds: Duration,
val taxRate: Double)
extends RegularPolicy(amount, seconds) // 조합될 클래스
with TaxablePolicy // 믹스인할 클래스
super
를 통해 다음 단계의 클래스나 트레이트 메서드를 호출TaxableRegularPolicy
의 경우 자신 → TaxablePolicy
트레이트 → RegularPolicy
class RateDiscountableAndTaxableRegularPolicy(
amount: Money,
seconds: Duration,
val discountAmount: Money,
val taxRate: Double)
extends RegularPolicy(amount, seconds) // 표준 요금제
with TaxablePolicy // 세금 정책을 적용 후
with RateDiscountablePolicy // 비율 할인 정책 적용
class TaxableAndRateDiscountableRegularPolicy(
amount: Money,
seconds: Duration,
val discountAmount: Money,
val taxRate: Double)
extends RegularPolicy(amount, seconds) // 표준 요금제에
with RateDiscountablePolicy // 비율 할인 정책 적용 후
with TaxablePolicy // 세금 정책 적용
new RegularPlicy(Money(100), Duration.fSeconds(10))
with RateDiscountPolicy
with TaxablePolicy {
val discountAmount = Money(100)
val taxRate = 0.02
}