装饰(Decorator)模式又叫做包装模式,动态地给一个对象添加额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。。
我们先来看一个快餐店的例子。
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
如果我们使用使用继承的方式去实现
存在的问题:
-
扩展性不好
如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。
-
产生过多的子类
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构- Component为统一接口,也是装饰类和被装饰类的基本类型。
- ConcreteComponent为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
- Decorator是装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。而Decorator本身,通常采用默认实现,他的存在仅仅是一个声明:我要生产出一些用于装饰的子类了。而其子类才是赋有具体装饰效果的装饰产品类。
- ConcreteDecorator是具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。可以通过构造器声明装饰哪种类型的ConcreteComponent,从而对其进行装饰。
我们使用装饰者模式对快餐店案例进行改进
快餐类为抽象类,炒面炒饭继承快餐类
Garnish 是一个装饰者,继承了快餐类,并且聚合了快餐类的对象
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:54
* @description: 快餐接口
*/
public abstract class FastFood {
private float price;
private String desc;
public FastFood() {
}
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public void setPrice(float price) {
this.price = price;
}
public float getPrice() {
return price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public abstract float cost(); //获取价格
}
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:55
* @description: 炒饭
*/
public class FriedRice extends FastFood {
public FriedRice() {
super(10, "炒饭");
}
public float cost() {
return getPrice();
}
}
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:55
* @description: 炒面
*/
public class FriedNoodles extends FastFood {
public FriedNoodles() {
super(12, "炒面");
}
public float cost() {
return getPrice();
}
}
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:55
* @description: 配料类
*/
public abstract class Garnish extends FastFood {
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:56
* @description: 鸡蛋配料
*/
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:56
* @description: 培根配料
*/
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(fastFood,2,"培根");
}
@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
package com.dongguo.decorator;
/**
* @author Dongguo
* @date 2021/8/22 0022-13:56
* @description:
*/
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");
System.out.println("========");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();
food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");
System.out.println("========");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}
运行结果:
炒饭 10.0元
========
鸡蛋炒饭 11.0元
========
培根炒面 14.0元
装饰模式的优点: 可以提供比继承更多的灵活性,可以通过一种动态地方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,而且具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类。
装饰模式的缺点: 使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承更加易于出错,排错也困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐,尽量减少装饰类的数量,以便降低系统的复杂度。
适用情况:
装饰模式是对模式的有力补充。继承不是万能的,在项目中如果考虑到已维护、易扩展、易复用等,而在一些情况下,使用继承增加了很多子类,这样灵活性非常差,维护起来也很困难,那么我们可以采用装饰模式替代继承,解决子类过多的问题,继承是静态的给类增加功能,而装饰模式则是动态地增加功能。
装饰模式的扩展性非常好,在项目中,随时面临着需求变更,那么我们可以通过装饰模式重新封装一个类,而不是通过继承来完成,对源程序没有改变,通过扩展很好的完成了这次变更。
JDK源码解析IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。
我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter
public class Demo {
public static void main(String[] args) throws Exception{
//创建BufferedWriter对象
//创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
BufferedWriter bw = new BufferedWriter(fw);
//写数据
bw.write("hello Buffered");
bw.close();
}
}
使用起来感觉确实像是装饰者模式,接下来看它们的结构:
BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。
代理和装饰者的区别静态代理和装饰者模式的区别:
-
相同点:
-
都要实现与目标类相同的业务接口
-
在两个类中都要声明目标对象
-
都可以在不修改目标类的前提下增强目标方法
-
-
不同点:
-
目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象
-
获取目标对象构建的地方不同 装饰者是由外界传递进来,可以通过构造方法传递 静态代理是在代理类内部创建,以此来隐藏目标对象
-