装饰模式(Decorator Pattern):
动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类的方式更为灵活。(Attach additional responsibilities to an object dynamically. Decorators provides a flexible alternative to subclasses for extending functionality.)
装饰模式(Decorator):也叫包装器模式(Wrapper),装饰模式是一种用于提代继承的技术,无需通过子类增加继承就能拓展对象的新功能;使用对象的关联关系提代继承关系,更加灵活,同时避免类型体系的快速膨胀。
开发中使用的场景:
- 典型应用场景就是:IO中输入流和输出流的设计;
- Swing包中图形界面构件功能;
- Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper类,增强了request对象的功能;
- Struts2中,request,response,session对象的处理
装饰模式的优缺点:
优点:
- 扩展对象功能,比继承灵活,不会导致类个数急剧增加;
- 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象;
- 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加
新的具体构件子类和具体装饰子类。
缺点:
- 产生很多小对象。大量小对象占据内存,一定程度上影响性能;
- 装饰模式易于出错,调试排查比较麻烦。
装饰模式和桥接模式的区别:
两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。
案例:
比如去吃小摊,主体有煎蛋,火腿片等等,每个配料的价格都不一样,不管你怎么配配料,最终价格是手抓饼基础价加上每一种所选配料价格的总和。小摊的价格单如下:
用装饰者模式即可解决此问题,主体是手抓饼和肉夹馍,而配料则是装饰者:类图如下:
功能的实现大概有以下5个步骤:
步骤1:定义Component(被装饰对象的基类)抽象组件:定义一个抽象接口,来规范准备附加功能的类
/**
* 定义一个对象接口,可以给这些对象动态地添加职责
*/
public abstract class Pancake {
public String desc = "我不是一个具体的煎饼";
public String getDesc(){
return desc;
}
public abstract double price();
}
步骤2:ConcreteComponent(具体被装饰对象)具体组件:将要被附加功能的类,实现抽象构件角色接口
- 手抓饼:
/**
* 定义一个对象,可以给这个对象添加一些职责:手抓饼
*/
public class TornCake extends Pancake {
public TornCake() {
desc = "手抓饼";
}
@Override
public double price() {
return 4;
}
}
- 肉夹馍:
/**
* 定义一个对象,可以给这个对象添加一些职责:肉夹馍
*/
public class Roujiamo extends Pancake {
public Roujiamo() {
desc = "肉夹馍";
}
@Override
public double price() {
return 6;
}
}
步骤3:Decorator(装饰者抽象类)抽象装饰者:维持一个指向Component实例的引用,并定义一个与Component接口一致的接口。
/**
* 持有对具体构件角色的引用并定义与抽象构件角色一致的接口
*/
public abstract class Condiment extends Pancake {
public abstract String getDesc();
}
步骤4:ConcreteDecorator(具体装饰者)具体装饰: 实现抽象装饰者角色,负责对具体构件添加额外功能。
- 煎蛋:
/**
* 具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。
* 煎蛋:FiredEgg
*/
public class FiredEgg extends Condiment {
private Pancake pancake;
@Override
public String getDesc() {
return pancake.getDesc()+",煎蛋";
}
@Override
public double price() {
return pancake.price()+2;
}
public FiredEgg(Pancake pancake) {//构造器
this.pancake = pancake;
}
}
- 火腿片
/**
* 具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。
* 火腿片:Ham
*/
public class Ham extends Condiment {
private Pancake pancake;
@Override
public String getDesc() {
return pancake.getDesc() + ", 火腿片";
}
@Override
public double price() {
return pancake.price() + 1.5;
}
public Ham(Pancake pancake) {
this.pancake = pancake;
}
}
- 松肉
/**
* 具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。
* 松肉:MeatFloss
*/
public class MeatFloss extends Condiment {
private Pancake pancake;
@Override
public String getDesc() {
return pancake.getDesc()+",松肉";
}
@Override
public double price() {
return pancake.price()+1;
}
public MeatFloss(Pancake pancake) {
this.pancake = pancake;
}
}
- 黄瓜丝
/**
* 具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。
* 黄瓜丝:Cucumber
*/
public class Cucumber extends Condiment {
private Pancake pancake;
@Override
public String getDesc() {
return pancake.getDesc()+",黄瓜丝";
}
@Override
public double price() {
return pancake.price()+0.5;
}
public Cucumber(Pancake pancake) {
this.pancake = pancake;
}
}
步骤5:测试
/**
* 测试类
* author https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class TestDecorator {
public static void main(String[] args) {
Pancake tornCake = new TornCake();
tornCake = new FiredEgg(tornCake);
tornCake = new MeatFloss(tornCake);
//手抓饼+煎蛋+松肉 的价格
System.out.println(String.format("%s ¥%s", tornCake.getDesc(), tornCake.price()));
Pancake roujiamo = new Roujiamo();
roujiamo = new FiredEgg(roujiamo);
roujiamo = new FiredEgg(roujiamo);
roujiamo = new Ham(roujiamo);
roujiamo = new MeatFloss(roujiamo);
roujiamo = new Cucumber(roujiamo);
//肉夹馍+其它 的价格
System.out.println(String.format("%s ¥%s", roujiamo.getDesc(), roujiamo.price()));
}
}
控制台打印结果如下:
至此,装饰模式的介绍,应用场景,优缺点,以及一个完整案例已介绍完毕!
如果想了解更多设计模式,可点击:设计模式概述 以及 23种设计模式的介绍