定义:
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:
一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:
在不想增加很多子类的情况下扩展类。
如何解决:
将具体功能职责划分,同时继承装饰者模式。
装饰者结构:
(1)抽象组件(Component):给出一个抽象接口,以规范准备接受附加责任的对象。
(2)被装饰者(ConcreteComponent):Component的具体实现,也就是我们要装饰的具体对象(真实对象,需要被进行扩展的对象)。
(3)装饰者组件(Decorator):持有组件对象的实例引用,该类的职责就是为了装饰具体组件对象,定义的基类(需要进行扩展的对象,有对真实对象的引用)。
(4)具体装饰(ConcreteDecorator):负责给构件对象装饰附加的功能(具体的增加新功能,具体的扩展)。
优点:
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:
多层装饰比较复杂。
使用场景
-
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
-
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
-
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
实例:
一个快餐店的例子。
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
使用继承的方式存在的问题:
- 扩展性不好
如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。 - 产生过多的子类
使用装饰者模式
类图
快餐店抽象类(抽象构件角色)
/**
* @author pzz
* @date 2022/5/15
* 快餐店抽象类
* 抽象构件角色
*/
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 abstract float cost();
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;
}
}
炒面类(具体的构件角色)
/**
* @author pzz
* @date 2022/5/15
* 炒面类
* 具体的构件角色
*/
public class FriedNoodles extends FastFood{
public FriedNoodles(){
super(12,"炒面");
}
@Override
public float cost() {
return getPrice();
}
}
炒饭类
/**
* @author pzz
* @date 2022/5/16
* 炒饭类
* 具体构件角色
*/
public class FriedRice extends FastFood{
public FriedRice(){
super(10,"炒饭");
}
@Override
public float cost() {
return getPrice();
}
}
装饰者抽象类(抽象装饰者角色)
/**
* @author pzz
* @date 2022/5/16
* 装饰者类
* 抽象装饰者角色
*/
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;
}
}
鸡蛋类(具体的装饰者角色)
/**
* @author pzz
* @date 2022/5/16
* 鸡蛋类
* 具体的装饰者角色
*/
public class Egg extends Garnish{
public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}
@Override
public float cost() {
//计算加上鸡蛋的价格
return getPrice() + getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
培根类
/**
* @author pzz
* @date 2022/5/16
* 培根类
* 具体的装饰者角色
*/
public class Bacon extends Garnish{
public Bacon(FastFood fastFood) {
super(fastFood, 2, "培根");
}
//计算加上培根的价格
@Override
public float cost() {
return getPrice() + getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
测试类
/**
* @author pzz
* @date 2022/5/16
*/
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood rice = new FriedRice();
System.out.println(rice.getDesc() + rice.cost() + "元");
System.out.println("---------------");
//鸡蛋炒饭
rice = new Egg(rice);
System.out.println(rice.getDesc()+rice.cost() + "元");
System.out.println("--------------");
//再加一个鸡蛋
rice = new Egg(rice);
System.out.println(rice.getDesc()+rice.cost() + "元");
System.out.println("---------------");
//培根炒饭
rice = new Bacon(rice);
System.out.println(rice.getDesc()+rice.cost() + "元");
}
}
测试结果
解释说明:
装饰者主要是给某个类加上职责,不需要改动原有的代码。
如再加个火腿肠的配料,直接继承装饰者抽象类即可,没必要继承炒饭类和炒面类。
当想加个炒粉时直接继承快餐店抽象类。装饰者模式可以双向扩展。
好处:
- 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
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子实现类进行了增强,添加了缓冲区,提高了写数据的效率。
代理和装饰者的区别
静态代理和装饰者模式的区别:
- 相同点:
- 都要实现与目标类相同的业务接口。
- 在两个类中都要声明目标对象。
- 都可以在不修改目标类的前提下增强目标方法。
- 不同点:
- 目的不同
装饰者是为了增强目标对象。
静态代理是为了保护和隐藏目标对象。 - 获取目标对象构建的地方不同
装饰者是由外界传递进来,可以通过构造方法传递。
静态代理是在代理类内部创建,以此来隐藏目标对象。
- 目的不同
结束!
感情有什么好讲的?爱自有天意。