4.装饰者模式
(1).概述
我们先来看一个快餐店的例子。
快餐店有炒面、炒饭这些快餐。可以额外的附加加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外的加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
使用继承的方式去
使用继承的方式存在的问题:
- 扩展性不好
如果要再一种配料(培根),我们就会发现需要给炒米和炒面条分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更更多的子类。 - 产生过多的子类
(2).结构
定义: 旨在不该现有对象结构的情况下,动态的给该对象增加一些职责(即增加其他额外的功能)的模式。
装饰模式的角色:
- 抽象构建角色: 定义一个
抽象接口
以规范准备接受附加责任的对象。 - 具体构建角色:
实现抽象构建
,通过装饰角色为其添加一些职责。 - 抽象装饰角色:
继承或实现抽象构建
,并包含具体构建的实列,可以通过其子类扩展具体构建的功能。 - 具体装饰角色:
实现抽象装饰
的相关方法,并给具体构建对象添加附加的责任。
(3).案列
我们使用装饰者模式对快餐店案列进行改进,体会装饰者模式的精髓。
类图如下:
抽象构建角色
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 17:52 * @PackageName:com.jsxs.structure.decorator * @ClassName: FastFood * @Description: TODO 抽象构建角色 * @Version 1.0 */ public abstract class FastFood { private float price; private String desc; public FastFood(float price, String desc) { this.price = price; this.desc = desc; } public FastFood() { } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public abstract float cost(); }
具体构建角色
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 17:57 * @PackageName:com.jsxs.structure.decorator * @ClassName: rice * @Description: TODO 炒饭具体构建 * @Version 1.0 */ public class rice extends FastFood{ public rice(){ //无参创建对象 super(10,"炒饭"); // 设定继承父类的参数 } @Override public float cost() { return this.getPrice(); } }
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 18:01 * @PackageName:com.jsxs.structure.decorator * @ClassName: noddles * @Description: TODO * @Version 1.0 */ public class noddles extends FastFood{ public noddles(){ super(20,"面条"); } @Override public float cost() { return this.getPrice(); } }
抽象装饰者类
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 18:02 * @PackageName:com.jsxs.structure.decorator * @ClassName: Granish * @Description: TODO 抽象装饰者类 * @Version 1.0 */ public abstract class Granish extends FastFood{ // 聚合快餐 private FastFood fastFood; public Granish(float price, String desc, FastFood fastFood) { super(price, desc); this.fastFood = fastFood; } public FastFood getFastFood() { return fastFood; } public void setFastFood(FastFood fastFood) { this.fastFood = fastFood; } }
具体装饰角色
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 18:06 * @PackageName:com.jsxs.structure.decorator * @ClassName: egg * @Description: TODO * @Version 1.0 */ public class egg extends Granish{ public egg(FastFood fastFood){ super(1,"鸡蛋",fastFood); //定义鸡蛋 } @Override public float cost() { return this.getPrice()+this.getFastFood().cost(); //鸡蛋价格+快餐价格 } @Override public String getDesc() { return super.getDesc()+getFastFood().getDesc(); // 鸡蛋描述+米饭描述 } }
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 18:20 * @PackageName:com.jsxs.structure.decorator * @ClassName: Bacon * @Description: TODO * @Version 1.0 */ public class Bacon extends Granish{ public Bacon(FastFood fastFood){ super(1,"培根",fastFood); //定义鸡蛋 } @Override public float cost() { return this.getPrice()+this.getFastFood().cost(); //鸡蛋价格+快餐价格 } @Override public String getDesc() { return super.getDesc()+getFastFood().getDesc(); // 鸡蛋描述+米饭描述 } }
客户端
package com.jsxs.structure.decorator; /** * @Author Jsxs * @Date 2023/4/21 18:22 * @PackageName:com.jsxs.structure.decorator * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { // 点一份炒饭 FastFood rice = new rice(); System.out.println(rice.getDesc()+rice.getPrice()); // 炒饭中加一个鸡蛋 rice = new egg(rice); System.out.println(rice.getDesc()+" "+rice.cost()); rice=new Bacon(rice); System.out.println(rice.getDesc()+" "+rice.cost()); } }
好处:
- 装饰者模式可以带来比较更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取不同行为状态的多样化的结构。装饰者模式比继承更具备灵活的扩展性,
完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
。 装饰类和被装饰类可以独立发展,不会相互耦合
,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
(4).使用场景
- 当
不能采用继承的方式
对系统进行扩充或者采用继承不利于系统扩展和维护时。
- 不能次啊用继承的情况主要有两类:
-(1).第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性
增长。
-(2).第二类是因为类定义不能继承(如 final)
- 在不影响其他对象的情况下,以
动态、透明的方式给单个对象添加职责
- 当对象的功能要求可以
动态地添加
,也可以再动态的撤销
时。
(5).JDK源码解析
IO流中的包装类使用到了装饰模式。BufferedInputStream
,BufferedOutputStream
,BufferedReader
,BufferedWriter
。
(6).代理和装饰者的区别
静态代理和装饰者模式的区别:
- 相同点:
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法
- 不同点
- 目的不同: “装饰者是为了增强目标对象,静态代理是为了保护和隐藏目标对象”
- 获取目标对象构建的地方不同:“装饰者是由
外界
传递进来的,可以通过构造方法传递,静态代理时在代理内部
创建,以此来隐藏目标对象”