第八章结构型模式—装饰者模式


结构型模式描述如何将类或对象按某种布局组成更大的结构,有以下两种:

  • 类结构型模式:采用继承机制来组织接口和类。
  • 对象结构型模式:釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足 “合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下 7 种:

  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

装饰者模式

解决的问题

img

  • 假如我们有一个初代的机器人,他具有对话,唱歌,放音乐的功能,如果我们想要一个新的功能,拖地和跳舞
  • 第一种方式就是我们用第二代机器人,第二代机器人利用继承了第一代机器的基础上进行扩展,如果我们想要新的功能,就需要第三代机器人(因为要满足开放闭合的原则)
    • 由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
    • 我们要引入一个有功能为飞行的机器人就需要重新定义一个类,如果有一天又想要一个游泳的功能,又得重新定义一个类,哪天不想要飞行功能了,又得重新定义一个类来只实现游泳
  • 第二种模式就用我们的装饰器模式,我们类似在初代机器人上套一个盒子,然后在盒子上进行功能的扩展 这样就可以扩展一个类的功能。 动态增加功能,动态撤销。
    • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

概念

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

结构

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

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

案例

在这里插入图片描述

先来看一个快餐店的例子:快餐店有炒面、炒饭等快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。下面是使用继承方式的类图:

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

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

使用装配者进行改进

采用使用装饰者模式对快餐店案例进行改进,类图如下:

image-20230504141909725

抽象构件角色—快餐类

@Data
public abstract class FastFood {
    private float price;//价格
    private String desc;//描述

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }
    public abstract float cost();
}

具体构建角色—炒饭 炒面

public class FireRice extends FastFood{

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

    @Override
    public float cost() {
        return getPrice();
    }
}
public class FireNoodles extends FastFood{
    public FireNoodles() {
        super(12, "炒面");
    }

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

抽象装饰者角色:配料类

public abstract class Garnish extends FastFood{
    //声明快餐类的变量
    private FastFood fastFood;

    public Garnish(FastFood fastFood,float price,String desc){
        super(price,desc);
        this.fastFood=fastFood;
    }

}
  • 为什么我们的配料类要求继承我们的FastFood? 有什么意义呢?等后面的测试时的分析

具体的装饰者角色 鸡蛋和培根

public class Egg extends Garnish {
    public Egg(FastFood fastFood) {
        super(fastFood, 1, "鸡蛋");
    }

    // 计算价格
    public float cost() {
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}
public class Bacon extends Garnish {
    public Bacon(FastFood fastFood) {
        super(fastFood, 2, "培根");
    }

    // 计算价格
    public float cost() {
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

测试

public class Client {
    public static void main(String[] args) {
        //  点一份炒饭
        FastFood fastFood=new FireNoodles();
        System.out.println(fastFood.getDesc() + "  " + fastFood.cost() + "元");
        //加一个鸡蛋
        fastFood=new Egg(fastFood);
        System.out.println(fastFood.getDesc() + "  " + fastFood.cost() + "元");
        //加一个培根
        fastFood=new Bacon(fastFood);
        System.out.println(fastFood.getDesc() + "  " + fastFood.cost() + "元");
    }
}
//炒面  12.0元
//鸡蛋炒面  13.0元
//培根鸡蛋炒面  15.0元
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
    • 就像一开始举例子的机器人,我们第二代的机器人不是在我们的第一代机器的基础上进行功能扩展,我们类似在初代机器人上套一个盒子,然后在盒子上进行功能的扩展 去掉箱子就取消对应的拓展
  • fastFood=new Egg(fastFood) 我们用的是new Egg,最后还是用fastFood去接收,这也是我们为什么要让配料去继承我们的FastFood,不然在这我们只能用配料类去接收返回的对象,而不是用传进去的fastFood对象
    • 我们本来的意思是给我们的炒面加一个鸡蛋。所以返回的应该还是我们的炒面,如果不实现继承,那么我们加个鸡蛋,最后返回了却是一个配料(鸡蛋),这是不符合我们的意图的

使用场景

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

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

    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。

    • 第二类是因为类定义不能继承(如 final 类)。

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

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

JDK源码分析

JDK源码解析
IO流中的包装类使用到了装饰者模式:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

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

// 创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
// 创建BufferedWriter对象
BufferedWriter bw = new BufferedWriter(fw);
// 写数据
bw.write("hello Buffered");
bw.close();

使用起来感觉确实像是装饰者模式,接下来看它们的结构

在这里插入图片描述

  • InputStreamWriter是抽象构件 FileWriter是具体构件
  • BufferWriter是具体装饰角色

BufferedWriter 使用装饰者模式对 Writer 子实现类进行了增强,添加了缓冲区,提高了写数据的效率。

静态代理和装饰者的区别

相同点:

  • 都要实现与目标类相同的业务接口

  • 在两个类中都要声明目标对象

  • 都可以在不修改目标类的前提下增强目标方法

不同点:

  • 目的不同

    • 装饰者是为了增强目标对象

      • 静态代理是为了保护和隐藏目标对象
  • 装饰者可以迭代增强,代理只能增强一次

    • 比如我们上面的例子,我们的炒饭可以加鸡蛋,然后再加一个培根
    • 而我们的代理,比如我们的日志功能,我们想要给某个类添加日志功能,我们直接进行调用代理对象进行功能增强
  • 获取目标对象构建的地方不同

    • 装饰者是由外界传递进来,可以通过构造方法传递
      • 我们的装饰着中的具体构建对象是由外部传入到我们的装配者中
    • 静态代理是在代理类内部创建,以此来隐藏目标对象
      • 我们的静态代理中的真实主题类对象是在我们的静态类内部中创建的。而不是由外部传入的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里不会投三分

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值