Decorator Pattern 可以令到不用改動原本物件的程式碼,而又可以擴充一些功能在這個物件上, 就好像 Plugin 一樣。你可以想像一下,WordPress 的 Plugin 並不需要改動核心的程式, 但又可以増加一些功能。 Java 上的 InputStream 一樣採用這種模式去實現。
看看以下例子應該有助你明白這個模式:
公司要你開發一個手機用戶計費系統,因應用戶使用時間而去收費, 使用時間愈多折扣亦愈多。而用戶亦有分普通用戶或 VIP 用戶, VIP 用戶亦有更多折扣。
一開始你可能會設計成這樣:
(User.java) download public class User {
private String name;
private boolean vip;
public User(String name, boolean vip) {
super();
this.name = name;
this.vip = vip;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isVip() {
return vip;
}
public void setVip(boolean vip) {
this.vip = vip;
}
}
|
(MobilePayment.java) download public class MobilePayment {
final double moneyPerMinute;
public MobilePayment(double moneyPerMinute){
this.moneyPerMinute = moneyPerMinute;
}
public double charge(User user, int minute) {
double fee = minute * moneyPerMinute;
if (minute >= 10000) {
fee *= 0.8;
} else if (minute >= 7000) {
fee *= 0.85;
} else if (minute >= 4000) {
fee *= 0.9;
} else if (minute >= 2000) {
fee *= 0.95;
}
if (user.isVip()) {
fee *= 0.9;
}
return fee;
}
}
|
(Main.java) download public class Main {
public static void main(String[] args) {
// $1.2 per minute
MobilePayment mobilePayment = new MobilePayment(1.2);
User user1 = new User("Lawrence", true);
User user2 = new User("Tony", false);
System.out.println(user1.getName() + " usage: 10000 minute, charge: "
+ mobilePayment.charge(user1, 10000));
System.out.println(user2.getName() + " usage: 4000 minute, charge: "
+ mobilePayment.charge(user2, 4000));
}
}
|
執行結果Lawrence usage: 10000 minute, charge: 8640.0
Tony usage: 4000 minute, charge: 4320.0
|
你可以看見所有計費的程式碼也在 MobilePayment.charge
內,如果再要增加計費條件 就要修改這個地方的程式碼。
現在公司又要增加一些收費條件,因為提供額外的服務給公司客戶,需要收取額外 10% 服務費。 如果你只改動 MobilePayment.charge
內的程式碼,那愈來愈多條件時就難以維護, 現在我們使用 Decorator Pattern 去解決這些問題,收費條件亦可以隨時抽換。
(User.java) download public class User {
private String name;
private boolean vip;
private boolean company;
public User(String name, boolean vip, boolean company) {
super();
this.name = name;
this.vip = vip;
this.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isVip() {
return vip;
}
public void setVip(boolean vip) {
this.vip = vip;
}
public boolean isCompany() {
return company;
}
public void setCompany(boolean company) {
this.company = company;
}
}
|
(Payment.java) download public interface Payment {
public double charge(User user, int minute);
}
|
(BasicMobilePayment.java) download public class BasicMobilePayment implements Payment {
final double moneyPerMinute;
public BasicMobilePayment(double moneyPerMinute){
this.moneyPerMinute = moneyPerMinute;
}
public double charge(User user, int minute) {
return minute * moneyPerMinute;
}
}
|
(AdditionalPayment.java) download public abstract class AdditionalPayment implements Payment {
protected Payment payment;
public AdditionalPayment(Payment payment){
this.payment = payment;
}
public abstract double charge(User user, int minute);
}
|
(UsageAmountPayment.java) download public class UsageAmountPayment extends AdditionalPayment {
public UsageAmountPayment(Payment payment) {
super(payment);
}
@Override
public double charge(User user, int minute) {
if (minute >= 10000) {
return payment.charge(user, minute) * 0.8;
} else if (minute >= 7000) {
return payment.charge(user, minute) * 0.85;
} else if (minute >= 4000) {
return payment.charge(user, minute) * 0.9;
} else if (minute >= 2000) {
return payment.charge(user, minute) * 0.95;
}
return payment.charge(user, minute);
}
}
|
(VIPPayment.java) download public class VIPPayment extends AdditionalPayment {
private final double discount;
public VIPPayment(Payment payment, double discount) {
super(payment);
this.discount = discount;
}
@Override
public double charge(User user, int minute) {
if (user.isVip()){
return payment.charge(user, minute) * discount;
}
return payment.charge(user, minute);
}
}
|
(CompanyPayment.java) download public class CompanyPayment extends AdditionalPayment {
private final double serviceFee;
public CompanyPayment(Payment payment, double serviceFee) {
super(payment);
this.serviceFee = 1 + serviceFee;
}
@Override
public double charge(User user, int minute) {
if (user.isCompany()){
return payment.charge(user, minute) * serviceFee;
}
return payment.charge(user, minute);
}
}
|
(Main.java) download public class Main {
public static void main(String[] args) {
// $1.2 per minute
Payment mobilePayment = new BasicMobilePayment(1.2);
Payment usageAmountPayment = new UsageAmountPayment(mobilePayment);
Payment vipPayment = new VIPPayment(usageAmountPayment, 0.9);
Payment companyPayment = new CompanyPayment(vipPayment, 0.1);
Payment payment = companyPayment;
User user1 = new User("Lawrence", true, false);
User user2 = new User("Tony", false, false);
User user3 = new User("ABC Company", false, true);
System.out.println(user1.getName() + " usage: 10000 minute, charge: "
+ payment.charge(user1, 10000));
System.out.println(user2.getName() + " usage: 4000 minute, charge: "
+ payment.charge(user2, 4000));
System.out.println(user3.getName() + " usage: 10000 minute, charge: "
+ payment.charge(user3, 10000));
}
}
|
執行結果Lawrence usage: 10000 minute, charge: 8640.0
Tony usage: 4000 minute, charge: 4320.0
ABC Company usage: 10000 minute, charge: 10560.0
|
這樣就可以隨時新增條件或移除條件而又不會影響到核心的程式碼。
以下的圖片是描述 Singleton Pattern 的 Class Diagram。(From Wiki)