查看原文
其他

钢铁侠也是个设计模式高手

宋学方 IT服务圈儿 2022-09-11

作者:宋学方

来源:IT服务圈儿原创作品,转载请联系微信jb_quaner。

什么是装饰器模式

装饰器模式的说明:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。原文是:

Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.

从这段话可以看到装饰器的特点:动态地为对象增加新的功能,即扩展原有对象的功能。

装饰器的优点,至少是相比于继承来说,“更有弹性”,怎么理解呢,装饰器模式相比生成子类更为灵活,简单理解就是继承扩展是静态的,是在编译期间确定的,而装饰器是“动态”的,是在运行时灵活装配出来的。

装饰器模式示例

就以美国电影《钢铁侠》为例,如果托尼·史塔克在没有造出战衣之前,在考虑想拥有一个能够喷火的功能,那么如果采用继承的方式,则需要他生下一个儿子,该儿子通过基因变异出现了喷火功能,如果他又想拥有飞行功能怎么办呢,原来那个儿子的基因已经确定了,不可能拥有飞行的功能了,怎么办呢,只能再生一个儿子,拥有飞行的基因才行,想想都不灵活。这时候装饰模式来解决这个问题,托尼·史塔克不需要“生儿子”动作那么大,只需要打造一个支持扩展功能的钢铁战衣(装饰器)就可以,他自身相当于是具体的待装饰的组件,当需要喷火时,只需要在战衣上增加喷火器就可以,如果又想飞行,只需要在战衣上增加飞行装置就行,当然,如果不想喷火了,拆掉就行了,灵活。

如下图所示为继承图:

如下图所示为装饰器模式图:

按照UML图来展示如下图所示:

代码如下所示:

1.人形接口定义了所有实现人形接口的子类均需要实现action方法。

public interface IPeopleLikeInterface {
    public void action();
}

2.TonyStark子类实现了IPeopleLikeInterface接口,定义了TonyStark所具有的行为能力:

public class TonyStark implements IPeopleLikeInterface {
    @Override
    public void action() {
        System.out.println("I am TonyStark");
    }
}

3.BattleSuit抽象类,同样实现了IPeopleLikeInterface 接口,与TonyStark不同的是,BattleSuit抽象类还包括一个IPeopleLikeInterface 接口的引用,该引用就是需要被装饰(或被增强)的对象的引用。

public    class BattleSuit implements  IPeopleLikeInterface {
    private IPeopleLikeInterface people;
    @Override
    public void action() {
    }
    public IPeopleLikeInterface getPeople() {
        return people;
    }
    public void setPeople(IPeopleLikeInterface people) {
        this.people = people;
    }
}

4.相应的FireSuit子类,继承自BattleSuit抽象类,实现了自己的action方法,该行为就是增强或装饰的功能。

public class FireSuit extends  BattleSuit {

    @Override
    public void action()
    {
        super.getPeople().action();
        System.out.println("Fire on !!");
    }
}

5.与FireSuit子类类似的是可飞行功能增强,如下所示:

public class FlySuit extends  BattleSuit {

    @Override
    public void action()
    {
        super.getPeople().action();
        System.out.println("Fly !!");
    }
}

至此对象间的结构构建完成,那么在实际使用过程中是如何动态增强TonyStark的功能的呢,如下图所示:

public class DecorateRun {
    public void main(String[] args)
    {
        /**
         * 初始化一个TonyStark对象
         */
        IPeopleLikeInterface tonyStark = new TonyStark();
        /**
         * 当需要使TonyStark具有喷火功能时
         */
        BattleSuit battleSuit = new FireSuit();
        battleSuit.setPeople(tonyStark);
        /**
         * 使用喷火功能
         */
        battleSuit.action();

        /**
         * 当需要使TonyStark具有飞行功能时
         */
        BattleSuit battleSuit2 = new FlySuit();
        battleSuit.setPeople(tonyStark);
        /**
         * 使用飞行功能
         */
        battleSuit2.action();
    }
}

从JAVA源码中探寻装饰器模式

好了,接下来我们看一下成熟的java 源码中流机制是如何使用装饰模式的。

java语言采用流的机制来实现输入/输出。所谓流,就是数据的有序流动,流可以是从某个源出来,到某个目的地去。根据流的方向可以将流分成输出流和输入流。程序通过输入流读取数据,通过输出流写出数据。例如:一个java程序可以使用FileInputStream类从一个磁盘文件读取数据,如下图:

我们先看一下Java输入流的类层次结构,如下图所示:

我们以红框标识的类为例,说明Java输入流如何使用装饰器设计模式。

首先看一下这几个类的用途。

我们以红框标识的类为例,说明Java输入流如何使用装饰器设计模式。

首先看一下这几个类的用途。

1.抽象类InputStream,是所有输入流的基类。

public abstract class InputStream implements Closeable

2.FileInputStream,从磁盘文件中直接读取文件,每次读取一个字节。

public
class FileInputStream extends InputStream
/**
 * Reads a byte of data from this input stream. This method blocks
 * if no input is yet available.
 *
 * @return     the next byte of data, or <code>-1</code> if the end of the
 *             file is reached.
 * @exception  IOException  if an I/O error occurs.
 */
public int read() throws IOException {
    return read0();
}

如下图所示为最基础用法:

FileInputStream inputStream = new FileInputStream("d://1.txt");
int len;
/**
 * 一次读取字节,每读取一个字节都要实现一次与硬盘的交互操作
 *
 */
while ((data= inputStream.read()) != -1) {

}

当然也可以每次读取更多字节,以减少与硬盘的IO交互次数,如下图所示:

FileInputStream inputStream = new FileInputStream("d://1.txt");
int len;
byte[] bs = new byte[1024];
//这里添加了一个缓存数组,每次从硬盘读取1024个字节,也就是说,每读取1024个字节才与硬盘实现一次交互
while ((len = inputStream.read(bs)) != -1) {
    
}

实际上java提供了一种更为快速的实现方案,即引入BufferInputStream,此时调用read方法仍然是获取1字节,但是有内存缓存中读取,超过8k后,再与硬盘做次交互,一次性读取缓存中8k如下图所示:

FileInputStream inputStream = new FileInputStream("d://1.txt");
BufferedInputStream bis = new BufferedInputStream(inputStream); //默认有8k的缓存    
int len;
byte[] bs = new byte[1024];
  
while ((len = bis.read()) != -1) {
 
}

很显然,BufferInputStream就是一个装饰器,FileInputStream一是原始对象,类似于TonyStark的角色,通过BufferInputStream来对FileInputStream的功能进行增强。

我们看一下InputStream,FileInputStram、BufferInputStream之间的依赖关系图,看是否是装饰器模式。

可以看到FilterInputStream持有一个InputStream的引用,具备了增强的类结构,

BufferedInputStream、DataInputStream、LineNumberInputStream、PushbackInputStream 均是实现FilterInputStream的具体装饰类,用于装饰或增强原始对象的功能。

装饰器模式各角色定义

至此,我们再搬出装饰器模式的各角色定义,如上图所标示:

(1)抽象构件,如InputStream类,抽象类,定义统一的接口。

(2) 具体构建(Concrete Component)角色:如FileInputStream原始流处理器扮演。它们实现了抽象构建角色所规定的接口,可以被装饰流处理器所装饰。

(3)  抽象装饰(Decorator)角色:如FilterInputStream,它实现了InputStream所规定的接口。(4)具体装饰(Concrete Decorator)角色,如DateInputStream、BufferedInputStream 等,实现原始对象功能增强。

关于作者

宋学方 一个略懂一二但饱含热情的编码人

作者:宋学方

原文链接:https://blog.csdn.net/songxuefang/article/details/105916838

声明:本文为 程序员架构 原创投稿,未经允许请勿转载。


1、Redis 帝国的神秘使者,竟然想改造 C 语言! 

2、Linux 内核终于可以 debug 了!

3、Linux 内核终于可以 debug 了!

4、Gson:我爸是谷歌

点分享

点点赞

点在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存