您现在的位置是:首页 >其他 >肝一肝设计模式【六】-- 装饰器模式网站首页其他

肝一肝设计模式【六】-- 装饰器模式

老六说编程 2023-07-24 00:00:06
简介肝一肝设计模式【六】-- 装饰器模式

系列文章目录

肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门



前言

我们知道设计模式可以分为三大类:创建型模式、结构型模式、行为型模式。上一节我们分析了结构型模式中的适配器模式,这一节我们继续分析另外一种结构型模式——装饰器模式


一、什么是装饰器模式

装饰器模式(Decorator Pattern),它允许在不改变已有对象的基础上动态地扩展其功能。通过将对象包装在装饰器对象中,可以在运行时为对象添加额外的行为,就增加功能来说,装饰器模式比生成子类更灵活。

装饰器模式的关键是使用组合而非继承来实现功能的扩展。通过将对象包装在装饰器对象中,可以按需添加或修改对象的行为,而无需改变原始对象或其他代码。

二、装饰器模式中的角色

在装饰器模式中,有一个核心的接口或抽象类,代表要被装饰的对象。

然后,创建一个装饰器类,它实现了相同的接口或继承了相同的抽象类,并在构造函数中接受被装饰的对象作为参数。

装饰器类中包含一个与被装饰对象相同的成员变量,用于保存被装饰的对象。

在装饰器类中,可以调用被装饰对象的方法,并可以在方法调用前后添加额外的逻辑。

以下是装饰器模式的几个关键角色:

  • Component(抽象组件):定义了被装饰对象的接口或抽象类。
  • ConcreteComponent(具体组件):实现了组件接口或抽象类,是被装饰的对象。
  • Decorator(装饰器):实现了组件接口或抽象类,并在构造函数中接受被装饰对象。装饰器可以调用被装饰对象的方法,并可以添加额外的功能。
  • ConcreteDecorator(具体装饰器):扩展了装饰器类,实现了额外的功能。

三、举个栗子

说了这么多的概念,感觉懂了又好像没懂,那就必须举个栗子了,我在第一次了解装饰器模式的时候,第一时间就想到了玩一款RPG游戏,你可以为你的角色穿上装备,也可以不穿装备硬刚ヽ( ̄▽ ̄)ノ
在这里插入图片描述
首先创建一个基础的角色类(Character)作为抽象组件,并定义一个角色的基本操作:

public interface Character {
    void attack();
}

然后我们再建一个具体的角色类,比如新建一个战士(Warrior),实现角色接口:

public class Warrior implements Character {
    @Override
    public void attack() {
        System.out.println("赤手空拳的攻击!");
    }
}

现在我们准备给我们的战士加点装备,比如使用武器或者戴上防具,这时就可以使用装饰器模式了。

首先先定义一个装饰器抽象类(Decorator),也实现了角色接口:

public abstract class Decorator implements Character {

    protected Character character;

    public Decorator(Character character) {
        this.character = character;
    }

    @Override
    public void attack() {
        character.attack();
    }
}

然后,创建具体的装饰器类来实现附加功能。
比如,创建一个武器装饰器类(WeaponDecorator):

public class WeaponDecorator extends Decorator {

    public WeaponDecorator(Character character) {
        super(character);
    }

    @Override
    public void attack() {
        super.attack();
        addWeapon();
    }

    private void addWeapon() {
        System.out.println("装上利维坦之斧削你!");
    }
}

再创建一个防具装饰器类(ArmorDecorator):守护者之盾

public class ArmorDecorator extends Decorator {

    public ArmorDecorator(Character character) {
        super(character);
    }

    @Override
    public void attack() {
        super.attack();
        addArmor();
    }

    private void addArmor() {
        System.out.println("戴上守护者之盾我防!");
    }
}

测试一下

public class DecoratorClient {
    public static void main(String[] args) {
        Character warrior = new Warrior();
		warrior.attack();  // 输出:赤手空拳的攻击!
		
		Character warriorWithWeapon = new WeaponDecorator(warrior);
		warriorWithWeapon.attack();  // 输出:赤手空拳的攻击!装上利维坦之斧削你!
		
		Character warriorWithWeaponAndArmor = new ArmorDecorator(warriorWithWeapon);
		warriorWithWeaponAndArmor.attack();  // 输出:赤手空拳的攻击!装上利维坦之斧削你!戴上守护者之盾我防!
    }
}

通过装饰器模式,我们可以动态地为玩具对象添加不同的装饰器,以实现不同的附加功能,而无需修改原有的玩具类或客户端代码。这种方式可以灵活地组合和扩展功能,同时保持代码的清晰

四、在JDK中的应用

JDK中使用装饰器模式的地方,一个典型的例子就是Java I/O库中使用 BufferedInputStreamBufferedOutputStream 类对输入流和输出流进行包装和装饰。

简单示例:

public class DecoratorClient {
    public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream("input.txt");
            FileOutputStream fileOutputStream = new FileOutputStream("output.txt");

            // 使用装饰器对输入流进行包装和装饰
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

            // 使用装饰器对输出流进行包装和装饰
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

            // 读取和写入数据
            int data;
            while ((data = bufferedInputStream.read()) != -1) {
                bufferedOutputStream.write(data);
            }

            // 关闭流
            bufferedInputStream.close();
            bufferedOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在示例当中,我们使用了 FileInputStreamFileOutputStream 来创建原始的输入流和输出流。然后,我们分别使用 BufferedInputStreamBufferedOutputStream 对这些原始流进行装饰。

BufferedInputStream为例,看一下源码摘要(省略了一些方法):

public class BufferedInputStream extends FilterInputStream {
    // ...

    public BufferedInputStream(InputStream in) {
        super(in);
        // ...
    }

    // ...
}

在上述代码中,BufferedInputStream 继承了 FilterInputStream 类,FilterInputStream 本身是一个抽象类,也是装饰器类。FilterInputStream 继承了 InputStream 抽象类,从而与原始的输入流进行连接。

构造函数 BufferedInputStream(InputStream in) 接受一个原始的输入流对象作为参数,并通过调用 super(in) 将其传递给父类 FilterInputStream 的构造函数。

通过这样的继承关系和构造函数的调用,BufferedInputStream 实际上是一个装饰器类,它可以包装和装饰任何实现了 InputStream 接口的输入流对象。

BufferedInputStream 通过添加缓冲功能来装饰输入流。它维护了一个内部缓冲区,通过调用原始输入流的 read() 方法将数据从原始流读入到缓冲区中,并通过提供额外的方法(如 read(byte[] b, int off, int len))来从缓冲区中读取数据,以提高读取的性能。


写在最后

装饰器模式的优点:

  • 可以在不修改已有代码的情况下扩展对象的功能。
  • 允许多个装饰器按照一定顺序进行组合,以实现复杂的功能组合。
  • 装饰器与被装饰对象之间是松耦合的,可以独立地进行扩展和修改。

装饰器模式的缺点:

  • 会导致类的增多,增加了代码复杂性,过度使用装饰器模式也可能使代码难以理解和维护。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。