Skip to content
On this page

装饰者模式

概述

快餐店例子

快餐店又炒面、炒饭这些快餐,可以额外附件鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的几乎通常不一样,那么计算总价就会显得比较麻烦。

image-20220925164822917

使用继承的方式存在的问题你:

  • 扩展性不好:如果要再增加一种配料(火腿肠),我们就会发现需要给FireRice和FireNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类
  • 产生过多的子类

定义

旨在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式

结构

装置者(Decorator)模式中的角色:

  • 抽象构建(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象
  • 具体构建(Concrete Component)角色:实现抽象构建,通过装饰角色为其添加一些职责
  • 抽象装饰(Decorator)角色:继承或实现抽象构建,并包含具体构建的实例,可以通过子类扩展具体构建的功能
  • 具体装饰(Concrete Decorator)角色:实现抽象装饰的相关方法,并给具体构建对象添加附加的责任

案例

使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓,其UML类图如下

image-20220925165732134

代码

抽象构件(FastFood)

java
package com.huangjiliang.design.heima.structure.decoration;

/**
 * 抽象构建角色
 */
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 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();

}

具体构件(FireRice、FireNoodles)

java
package com.huangjiliang.design.heima.structure.decoration;

/**
 * 具体构建角色
 */
public class FireRice extends FastFood {

    public FireRice() {
        super(10F, "炒饭");
    }

    @Override
    public Float cost() {
        return getPrice();
    }
}

package com.huangjiliang.design.heima.structure.decoration;

/**
 * 具体构建角色
 */
public class FireNoodles extends FastFood {

    public FireNoodles() {
        super(12F, "炒面");
    }

    @Override
    public Float cost() {
        return getPrice();
    }
}

抽象构件(Garnish)

java
package com.huangjiliang.design.heima.structure.decoration;

/**
 * 抽象装饰角色
 */
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;
    }
}

抽象构件(Egg、Bacon)

java
package com.huangjiliang.design.heima.structure.decoration;

/**
 * 具体装饰角色
 */
public class Egg extends Garnish{

    public Egg(FastFood fastFood) {
        super(fastFood, 1F, "鸡蛋");
    }

    @Override
    public Float cost() {
        // 鸡蛋价格 + 炒饭|炒面价格
        return getPrice() + getFastFood().getPrice();
    }
}

package com.huangjiliang.design.heima.structure.decoration;

/**
 * 具体装饰角色
 */
public class Bacon extends Garnish{

    public Bacon(FastFood fastFood) {
        super(fastFood, 2F, "培根");
    }

    @Override
    public Float cost() {
        // 培根价格 + 炒饭|炒面价格
        return getPrice() + getFastFood().getPrice();
    }
}

客户端(Client)

java
package com.huangjiliang.design.heima.structure.decoration;

public class Client {

    public static void main(String[] args) {
        FastFood food = new FireRice();
        // 只点一份炒饭
        System.out.println(food.getDesc() + "  " + food.cost() + "");

        System.out.println("=================");

        FastFood food1 = new FireRice();
        food1 = new Egg(food1);
        // 点一份炒饭 + 鸡蛋
        System.out.println(food1.getDesc() + "  " + food1.cost() + "");

        System.out.println("=================");

        FastFood food2 = new FireNoodles();
        food2 = new Bacon(food2);
        // 点一份炒面 + 培根
        System.out.println(food2.getDesc() + "  " + food2.cost() + "");

    }

}

优点

  • 装饰者模式可以带来比继承更加灵活的扩展功能,使用更加方便,可以通过组合不同的装饰者来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具有良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,驻阿努故事模式可以动态扩展一个实现类的功能

使用场景

  1. 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时

    不能采用继承的情况主要有两类:

    • 第一类是系统中存在大量独立的扩展,为支持每一个组合将产生大量的子类,使得子类数目呈爆炸式增长
    • 第二类是因为类定义不能继承(如:final类)
  2. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责

  3. 当对象的功能要求可以动态的添加,也可以再动态的撤销时

JDK源码解析

IO流中的包装类使用到了装饰者模式。BufferedInputStream、BufferedOutStream、BufferedReader、BufferedWriter

以BufferedWriter举例说明,看看如何使用BufferedWriter

java
public class Demo{
    public static void main(String[] args) throws Exception {
        // 创建BufferedWriter对象
        // 创建FileWriter对象
        FileWriter fw = new FileWriter("C:\\Users\\黄继良\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        //写数据
        bw.write("hello Buffered");

        bw.close();
    }
}

其UML类图构造如下:

image-20220925165356808

总结

BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。如上述案例:Garnish即继承FastFood也聚合与FastFood,而后增加了FastFood的子实现类(FireRice、FireNoodeles)的功能

代理模式和装饰者模式的区别

静态代理和装饰者模式的区别:

  • 相同点:
    1. 都要实现与目标类相同的业务接口
    2. 在两个类中都要声明目标对象
    3. 都可以在不修改目标类的前提下增强目标方法
  • 不同点
    1. 目的不同:装饰者模式是为了增强目标对象,而静态代理是为了保护和隐藏目标对象
    2. 获取目标对象构件的地方不同:装饰者模式是由外界传递过来,可以通过构造方法传递;静态代理是在代理类内部创建,一次隐藏目标对象