本篇我们来介绍一下结构型模式中的装饰者模式。我们在购买机票的时候除了机票之外还可以选择购买一些增值服务,例如航空延误险,接送机券,机场贵宾室等等。如果使用继承来实现此业务场景,会导致有大量类,例如机票套餐类,机票+航空延误险套餐类,机票+接送机券套餐类,机票+航空延误险+机场贵宾室套餐类等等。使用装饰者模式只需为每个增值服务提供一个实现类就可以实现动态添加增值服务的功能。
装饰者模式
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
类图
- 抽象构件角色:规范负责接收附加责任的对象,例如PlaneTicket类;
- 具体构件角色:将要接收附加责任的类,即被装饰类,例如FromSZToBJ类和FromSZToCD类;
- 装饰角色:持有一个抽象构件的引用,且一般继承抽象构件角色,例如ValueAddedService类;
- 具体装饰角色:负责给构件对象添加附加责任,即装饰者,例如AviationDelayInsurance类,VIPRoom类和AirportTransferTicket类。
Java IO中的InputStream和OutputStream以及他们的子类就是标准的装饰者模式,观察这两个类及其子类可以更好的理解装饰者模式,懂得装饰者模式也更好的理解字节流这么多类之间的联系,关于字节流可以看这篇文章。
具体实现
抽象构件角色:
abstract class PlaneTicket {
protected BigDecimal price;
protected String name;
abstract String printOriginAndDestination();
abstract BigDecimal printPrice();
public void printTicketInf() {
System.out.println("您购买的机票即增值服务有:"+printOriginAndDestination());
System.out.println("总共需"+printPrice()+"元...");
}
}
具体构件角色:
class FromSZToBJ extends PlaneTicket{
FromSZToBJ() {
this.name = "深圳到北京";
this.price = new BigDecimal("1000");
}
@Override
String printOriginAndDestination() {
return this.name;
}
@Override
BigDecimal printPrice() {
return this.price;
}
}
class FromSZToCD extends PlaneTicket {
FromSZToCD() {
this.name = "深圳到成都";
this.price = new BigDecimal("800");
}
@Override
String printOriginAndDestination() {
return this.name;
}
@Override
BigDecimal printPrice() {
return this.price;
}
}
装饰角色:
abstract class ValueAddedService extends PlaneTicket {
protected PlaneTicket planeTicket;
public ValueAddedService(PlaneTicket planeTicket) {
this.planeTicket = planeTicket;
}
@Override
BigDecimal printPrice() {
return this.planeTicket.price;
}
@Override
String printOriginAndDestination() {
return this.planeTicket.printOriginAndDestination();
}
}
具体装饰角色:
class AviationDelayInsurance extends ValueAddedService {
public AviationDelayInsurance(PlaneTicket planeTicket) {
super(planeTicket);
this.name = "航空延误券";
this.price = new BigDecimal("30");
}
@Override
String printOriginAndDestination() {
return this.planeTicket.printOriginAndDestination() + "+" +this.name;
}
@Override
BigDecimal printPrice() {
return this.planeTicket.printPrice().add(this.price);
}
}
class VIPRoom extends ValueAddedService {
public VIPRoom(PlaneTicket planeTicket) {
super(planeTicket);
this.name = "机场贵宾室";
this.price = new BigDecimal("50");
}
@Override
String printOriginAndDestination() {
return this.planeTicket.printOriginAndDestination() + "+" +this.name;
}
@Override
BigDecimal printPrice() {
return this.planeTicket.printPrice().add(this.price);
}
}
class AirportTransferTicket extends ValueAddedService {
public AirportTransferTicket(PlaneTicket planeTicket) {
super(planeTicket);
this.name = "接送机券";
this.price = new BigDecimal("80");
}
@Override
String printOriginAndDestination() {
return this.planeTicket.printOriginAndDestination() + "+" +this.name;
}
@Override
BigDecimal printPrice() {
return this.planeTicket.printPrice().add(this.price);
}
}
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
PlaneTicket fromSZToBJ = new FromSZToBJ();
fromSZToBJ = new AviationDelayInsurance(fromSZToBJ);
fromSZToBJ = new AirportTransferTicket(fromSZToBJ);
fromSZToBJ.printTicketInf();
PlaneTicket fromSZToCD = new FromSZToCD();
fromSZToCD = new AirportTransferTicket(fromSZToCD);
fromSZToCD = new VIPRoom(fromSZToCD);
fromSZToBJ = new AviationDelayInsurance(fromSZToCD);
fromSZToBJ.printTicketInf();
}
//输出结果
您购买的机票即增值服务有:深圳到北京+航空延误券+接送机券
总共需1110元...
您购买的机票即增值服务有:深圳到成都+接送机券+机场贵宾室+航空延误券
总共需960元...
可以看到装饰者模式确实达到了动态添加附加责任的功能,并且有新的具体构件角色和具体装饰者角色直接继承抽象构件角色和装饰者角色就可以,符合开闭原则。但缺点也是显而易见的,这几个类之间的关系变得更复杂,更难懂。(我之前了解装饰者模式之前一直是搞不懂IO中的字节流这么多类之间的关系的...)
最后建议有时间可以看看字节流中的类分别对应哪个角色,最好可以自己想一个业务场景实现一下,好处很多。
应用场景
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责;
- 动态地为对象增加功能,这些功能也可以动态地撤销。