装饰者模式

装饰者模式概述

装饰者(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章:装饰者模式 | 作者:阎宏





posted @ 2017-04-22 15:15  xpeng_V  阅读(173)  评论(0编辑  收藏  举报