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