裝飾者模式

裝飾者模式概述

裝飾者(Decorator)模式又被稱爲包裝模式。對客戶端透明的方式擴展對象的功能。是繼承關係的一種替代方案。可以不通過繼承增加子類來擴展對象的新功能,使用對象之間的關聯關係代替繼承關係,更加靈活,避免了類數量的爆炸。

裝飾者模式結構

裝飾者模式類圖:


裝飾者模式中的角色有:
  1. 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
  2. 具體構件(ConcreteComponent)角色:定義一個要接收附加責任的類。
  3. 裝飾角色(Decorator)角色:持有一個(Component)對象的實例,並定義一個與抽象構件一致的接口。
  4. 具體裝飾者(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();
	}
}
輸出結果如下:


總結

在什麼條件下使用裝飾者模式更加合理?

  1. 需要擴展一個類的功能,或者給一個類委派其他責任。
  2. 需要動態地給一個類增加功能,並且這些功能可以撤銷。
  3. 需要增加由基本功能的組合而實現的複雜功能,但使用繼承較難實現。

裝飾者模式的優缺點

優點:
  1. 裝飾者模式和繼承都能達到擴展功能的目的,但是裝飾者模式更加的靈活。裝飾者模式可以隨時去掉不需要的功能。
  2. 使用裝飾者模式可以輕易的對各個具體裝飾進行先後順序的排列。
缺點:
  1. 使用裝飾者模式雖然可以明顯減少類的數量,但是隨之而來的是類之間的關係變得複雜。

參考文檔

《java與模式》 | 第26章:裝飾者模式 | 作者:閻宏





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章