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