装饰器模式

一、定义

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。

 

 

 

在类图中,各个角色的说明如下:
Component,抽象构件:Component是一个接口或者抽象类,是定义我们最核心的对象,也可以说是最原始的对象
ConcreteComponent,具体构件,或者基础构件:ConcreteComponent是最核心、最原始、最基本的接口或抽象类Component的实现,可以单独用,也可将其进行装饰
Decorator,装饰角色:一般是一个抽象类,继承自或实现Component,在它的属性里面有一个变量指向Component抽象构件,我觉得这是装饰器最关键的地方。
ConcreteDecorator,具体装饰角色:ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,它们可以把基础构件装饰成新的东西。

二、装饰器模式的应用场景

 装饰器模式其实在生活和生产中的案例特别多,例如IO文件流就是一个很直接简单的装饰器模式,在生活中也有,例如给窗户挂窗帘,可以给窗帘上加个小挂件装饰下,装饰完后觉得一个挂件不够再装一个。山东的姐夫让我想起了吃货的一个场景。山东人吃煎饼,喜欢在里面包东西,每个人想在里面包的菜都不一样,这里面就可以以大饼和前面说的窗帘为主体,挂件菜啥的就是装饰。

 

//抽象构件
public abstract class Component {
    // 抽象的方法
    public abstract void cost();
}
//具体基础构件
public class ConcreteComponent extends Component{
    public void cost(){
        System.out.println("这是具体基础构件");
    }
}
//抽象装饰角色
public abstract class Decorator  extends Component{
    private Component component = null;
    public Decorator(Component component){
        this.component = component;
    }
    @Override
    public void cost(){
        this.component.cost();
    }
}
//具体装饰角色
public class ConcreteDecorator extends Decorator{
    public ConcreteDecorator(Component component){
        super(component);
    }

    // 定义自己的修饰逻辑
    private void decorateMethod(){
        System.out.println("定义自己的修饰逻辑");
    }

    // 重写父类的方法
    public void cost(){
        this.decorateMethod();
        super.cost();
    }
}
public class DecoratorDemo {
    public static void main(String[] args){
        Component component = new ConcreteComponent();
        // 第一次修饰,比如,加一个花边
        component = new ConcreteDecorator(component);
        // 第二次修饰,比如,加一个图片
        component = new ConcreteDecorator(component);
        // 修饰后运行,将所有加一起
        component.cost();
    }
}

三、 装饰器模式在Java I/O系统中的实现

前面说过IO用的就是装饰器

 

 

InputStream作为抽象构件,其下面大约有如下几种具体基础构件,从不同的数据源产生输入:

  • ByteArrayInputStream,从字节数组产生输入;
  • FileInputStream,从文件产生输入;
  • StringBufferInputStream,从String对象产生输入;
  • PipedInputStream,从管道产生输入;
  • SequenceInputStream,可将其他流收集合并到一个流内;

FilterInputStream作为装饰器在JDK中是一个普通类,其下面有多个具体装饰器比如BufferedInputStream、DataInputStream等。我们以BufferedInputStream为例,使用它就是避免每次读取时都进行实际的写操作,起着缓冲作用。我们可以在这里稍微深入一下,站在源码的角度看下。

FilterInputStream内部封装了基础构件:

protected volatile InputStream in;

而BufferedInputStream在调用其read()读取数据时会委托基础构件来进行更底层的操作,而它自己所起的装饰作用就是缓冲,在源码中可以很清楚的看到这一切:

public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }


    private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            if (markpos > 0) {  /* can throw away early part of the buffer */
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                markpos = -1;   /* buffer got too big, invalidate mark */
                pos = 0;        /* drop buffer contents */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        // 看这行就行了,委托基础构件来进行更底层的操作
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

    private InputStream getInIfOpen() throws IOException {
        InputStream input = in;
        if (input == null)
            throw new IOException("Stream closed");
        return input;
    }

这部分的代码很多,这里没有必要考虑这段代码的具体逻辑,只需要看到在BufferedInputStream的read方法中通过getInIfOpen()获取基础构件从而委托其进行更底层的操作(在这里是读取单个字节)就可以说明本文所要说的一切了。至于I/O类库中的其他设计诸如OutputStream、Writer、Reader,是一致的

四、总结

装饰者模式的优缺点
优点:
1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3、装饰者完全遵守开闭原则。
缺点:
1、会出现更多的代码,更多的类,增加程序复杂性。
2、动态装饰时,多层装饰时会更复杂。
补充:代理模式和装饰器模式看起来很像,但是这两种设计模式所面向的功能扩展不一样,装饰器模式强调自身功能的扩展,Decorator所做的就是增强ConcreteComponent的功能,主体对象为ConcreteComponent,着重功能的变化;代理模式强调对代理过程过程的控制,Proxy完全掌握对RealSubject的访问控制,因此Proxy可以决定对RealSubject进行功能的扩展
 

  

posted @ 2021-05-09 22:09  童话述说我的结局  阅读(97)  评论(0编辑  收藏  举报