裝飾者模式概述
裝飾者(Decorator)模式又被稱爲包裝模式。對客戶端透明的方式擴展對象的功能。是繼承關係的一種替代方案。可以不通過繼承增加子類來擴展對象的新功能,使用對象之間的關聯關係代替繼承關係,更加靈活,避免了類數量的爆炸。
裝飾者模式結構
裝飾者模式類圖:
裝飾者模式中的角色有:
- 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
- 具體構件(ConcreteComponent)角色:定義一個要接收附加責任的類。
- 裝飾角色(Decorator)角色:持有一個(Component)對象的實例,並定義一個與抽象構件一致的接口。
- 具體裝飾者(ConcreteDecorator)角色:負責來裝飾構件對象,增加新的功能。
一個例子:發票系統
1、需求
一家書店需要打印顧客所購買的商品的發票,一張發票可以分爲三個部分:
- 發票頭部:包含顧客的名稱,和日期。
- 發票主體:銷售的貨物清單,包括商品的名字,購買的數量,單價和小計。
- 發票尾部:商品的總金額等。
一張完整的發票大致如下:
新華書店
顧客姓名:xpeng_V 日期:2017年4月22日
--------------------------------------------------
書名 價格 數量 小計
《java程序設計》 23 1 23
《人性的弱點》 30 2 60
--------------------------------------------------
總金額:83
實際的場景中,發票的頭部和尾部可能有多重樣式,因此係統的設計必須給出足夠的靈活性,使得一個新的發票頭或發票尾可以輕鬆的替換原來的樣式,並且不會影響原來的系統。
2、使用裝飾者模式
對於功能的擴展而言,裝設者模式是一種靈活的,可替代繼承的選擇。發票的頭部和尾部分別可以使用HeaderDecorator和FooterDecorator來代表。有多少種頭和尾就可以有多少種具體的裝飾類。
首先定義購買的商品,並且把購買的每一種商品打印在發票上:
public class OrderLine {
private String itemName; // 商品名稱
private int units; // 商品數量
private double unitPrice; // 商品單價
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getUnits() {
return units;
}
public void setUnits(int units) {
this.units = units;
}
public double getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(double unitPrice) {
this.unitPrice = unitPrice;
}
// 返回該種商品的小計金額
public double getTotalAmt() {
return units * unitPrice;
}
public void printLine() {
System.out.println(itemName + " " + unitPrice + " " + units + " "
+ getTotalAmt());
}
}
接着是抽象構件Order,包含了添加商品和刪除商品的操作,以及將每一種商品的明細打印在發票上:
public abstract class Order {
private OrderLine orderLine; // 發票主體中的一種商品
protected String customerName; // 客戶名稱
protected String salesDate;
private ArrayList<OrderLine> list = new ArrayList<OrderLine>();
// 打印發票
public void print() {
for (int i = 0; i < list.size(); i++) {
OrderLine orderLine = list.get(i);
// 打印該種商品的明細
orderLine.printLine();
}
}
// 購買一種商品
public void addList(OrderLine orderLine) {
list.add(orderLine);
}
// 捨棄一種商品
public void removeList(OrderLine orderLine) {
list.remove(orderLine);
}
// 獲得所有商品的總金額
public double getTotal() {
double totalAmt = 0.0D;
for (int j = 0; j < list.size(); j++) {
OrderLine line = list.get(j);
totalAmt += line.getTotalAmt(); // 每種商品的金額總和
}
return totalAmt;
}
public OrderLine getOrderLine() {
return orderLine;
}
public void setOrderLine(OrderLine orderLine) {
this.orderLine = orderLine;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getSalesDate() {
return salesDate;
}
public void setSalesDate(String date) {
this.salesDate = date;
}
public ArrayList<OrderLine> getList() {
return list;
}
public void setList(ArrayList<OrderLine> list) {
this.list = list;
}
}
具體構件類,這是最原始的發票,只包含了各種商品的明細,並沒有發票的頭部和尾部的裝飾:
/**
* 發票的主體
* @author xpeng_V
*
*/
public class SalesOrder extends Order {
public SalesOrder() {
}
@Override
public void print() {
super.print();
}
}
抽象裝飾角色:
/**
* 抽象裝飾角色
* @author xpeng_V
*
*/
public abstract class OrderDecorator extends Order {
protected Order order;
public OrderDecorator(Order order) {
this.order = order;
this.setSalesDate(order.getSalesDate());
this.setCustomerName(order.getCustomerName());
}
@Override
public void print() {
super.print();
}
}
具體裝飾角色:HeaderDecorator裝飾發票頭:
public class HeaderDecorator extends OrderDecorator {
public HeaderDecorator(Order order) {
super(order);
}
@Override
public void print() {
// 裝飾發票主體,增加發票頭部
this.printHeader();
super.order.print();
}
// 發票頭部裝飾方法
private void printHeader() {
System.out.println(" 新華書店");
System.out.println("顧客姓名:" + order.getCustomerName() + " "
+ "日期:" + order.getSalesDate());
System.out
.println("--------------------------------------------------");
System.out.println(" 書名 價格 數量 小計");
}
}
具體裝飾角色,FooterDecorator裝飾發票尾:
public class FooterDecorator extends OrderDecorator {
public FooterDecorator(Order order) {
super(order);
}
@Override
public void print() {
super.order.print();
// 裝飾發票主體,增加發票尾部
printFooter();
}
// 發票尾部裝飾方法
private void printFooter() {
System.out.println("--------------------------------------------------");
System.out.println(" 總金額:" + order.getTotal());
}
}
客戶端類:
首先,初始化客戶的名稱以及購買時間,然後添加兩種不同的商品,最後先使用“發票尾”裝飾發票,再使用“發票頭”裝飾發票,代碼如下:
public class App {
private static Order order;
public static void main(String[] args) {
order = new SalesOrder();
// 發票的日期
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
order.setSalesDate(dateFormat.format(new Date()));
//發票中的客戶名稱
order.setCustomerName("xpeng_V");
//增加第一種商品
OrderLine orderLine1 = new OrderLine();
orderLine1.setItemName("《簡愛》");
orderLine1.setUnits(4);
orderLine1.setUnitPrice(4.0D);
order.addList(orderLine1);
//增加第二種商品
OrderLine orderLine2 = new OrderLine();
orderLine2.setItemName("《地理》");
orderLine2.setUnits(3);
orderLine2.setUnitPrice(9);
order.addList(orderLine2);
order = new HeaderDecorator(new FooterDecorator(order));
//打印發票
order.print();
}
}
輸出結果如下:
總結
在什麼條件下使用裝飾者模式更加合理?
- 需要擴展一個類的功能,或者給一個類委派其他責任。
- 需要動態地給一個類增加功能,並且這些功能可以撤銷。
- 需要增加由基本功能的組合而實現的複雜功能,但使用繼承較難實現。
裝飾者模式的優缺點
優點:
- 裝飾者模式和繼承都能達到擴展功能的目的,但是裝飾者模式更加的靈活。裝飾者模式可以隨時去掉不需要的功能。
- 使用裝飾者模式可以輕易的對各個具體裝飾進行先後順序的排列。
缺點:
- 使用裝飾者模式雖然可以明顯減少類的數量,但是隨之而來的是類之間的關係變得複雜。
參考文檔
《java與模式》 | 第26章:裝飾者模式 | 作者:閻宏