【源码分析设计模式 5】Java I/O系统中的装饰器模式

一、基本介绍

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

二、装饰器模式的结构

1、Component,抽象构件

Component是一个接口或者抽象类,是定义我们最核心的对象,也可以说是最原始的对象,比如街边小吃;

2、ConcreteComponent,具体构件,或者基础构件

ConcreteComponent是最核心、最原始、最基本的接口或抽象类Component的实现,可以单独用,也可将其进行装饰,比如街边小吃最有名的手抓饼;

3、Decorator,装饰角色

 一般是一个抽象类,继承自或实现Component,在它的属性里面有一个变量指向Component抽象构件,我觉得这是装饰器最关键的地方。

4、ConcreteDecorator,具体装饰角色

ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,它们可以把基础构件装饰成新的东西,比如把一个普通的手抓饼装饰成加蛋、加肠儿、金针菇的手抓饼。

三、装饰器模式优缺点

1、优点

(1)装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。

(2)装饰器模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component(因为Decorator本身就是继承自Component的),实现的还是is-a的关系。

2、缺点

(1)装饰器模式虽然减少了类的爆炸,但是在使用的时候,你就可能需要更多的对象来表示继承关系中的一个对象

(2)装饰器模式虽然从数量级上减少了类的数量,但是为了要装饰,仍旧会增加很多的小类这些具体的装饰类的逻辑将不会非常的清晰,不够直观,容易令人迷惑。

(3)多层的装饰是比较复杂的。为什么会复杂?你想想看,就像剥洋葱一样,你剥到最后才发现是最里层的装饰出现了问题,可以想象一下工作量。这点从我使用Java I/O的类库就深有感受,我只需要单一结果的流,结果却往往需要创建多个对象,一层套一层,对于初学者来说容易让人迷惑。

四、装饰器模式的使用场景

1、当你想要给一个类增加功能,然而,却并不想修改原来类的代码时,可以考虑装饰器模式如果你想要动态的给一个类增加功能,并且这个功能你还希望可以动态的撤销,就好像直接拿掉了一层装饰物;

2、比如java里面的基本数据类型int、boolean、char....都有它们对应的装饰类Integer、Boolean、Character....

3、在Java IO中,具体构建角色是节点流、装饰角色是过滤流;

FilterInputStream和FilterOutputStream是装饰角色,而其他派生自它们的类则是具体装饰角色。

DataoutputStream out=new DataoutputStream(new FileoutputStream());

这就是 装饰者模式,DataoutputStream是装饰者子类,FileoutputStream是实现接口的子类。

这里不会调用到装饰者类--FilteroutputStream,只是作为继承的另一种方案,对客户端来说是透明的,是为了功能的扩张。

五、装饰器模式实现手抓饼

老板,来个手抓饼,加个蛋、加根烤肠多少钱?

这个就是装饰器模式,用蛋和烤肠去装饰手抓饼,让手抓饼更加美味。

1、Component,抽象构件:街边小吃

package designMode.advance.decorator;

public abstract class Snack {
    public String des; // 描述
    private float price = 0.0f;
    public String getDes() {
        return des;
    }
    public void setDes(String des) {
        this.des = des;
    }
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }

    //计算费用的抽象方法
    //子类来实现
    public abstract float cost();
}

2、ConcreteComponent,具体构件,或者基础构件

(1)手抓饼

package designMode.advance.decorator;

public class HandGrabCake extends Snack {
    public HandGrabCake() {
        setPrice(5.0f);
        setDes(" 手抓饼 "+cost());
    }

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

(2)烤冷面

package designMode.advance.decorator;

public class GrilledColdNoodles extends Snack {
    public GrilledColdNoodles() {
        setPrice(4.0f);
        setDes(" 烤冷面 "+cost());
    }
    @Override
    public float cost() {
        return super.getPrice();
    }
}

Decorator,装饰角色

package designMode.advance.decorator;

public class Decorator extends Snack {
    private Snack obj;
    public Decorator(Snack obj) { //组合
        this.obj = obj;
    }
    @Override
    public float cost() {
        return super.getPrice() + obj.cost();
    }

    @Override
    public String getDes() {
        // obj.getDes() 输出被装饰者的信息
        return des + " " + getPrice() + " && " + obj.getDes();
    }
}

3、具体装饰角色

(1)鸡蛋

package designMode.advance.decorator;

public class Egg extends Decorator {
    public Egg(Snack obj) {
        super(obj);
        setDes(" 鸡蛋 ");
        setPrice(1.0f);
    }
}

(2)烤肠

package designMode.advance.decorator;

public class Sausage extends Decorator  {
    public Sausage(Snack obj) {
        super(obj);
        setDes(" 烤肠 ");
        setPrice(2.0f);
    }
}

(3)金针菇

package designMode.advance.decorator;

public class NeedleMushroom extends Decorator{
    public NeedleMushroom(Snack obj) {
        super(obj);
        setDes(" 金针菇 ");
        setPrice(2.5f);
    }
}

4、老板,来个手抓饼,加2个蛋、加1根烤肠

package designMode.advance.decorator;

public class HandGrabCakeBar {
    public static void main(String[] args) {
        // 装饰者模式下的订单:2个蛋+一根烤肠的手抓饼
        // 1. 点一份手抓饼
        Snack order = new HandGrabCake();
        System.out.println("小白手抓饼费用=" + order.cost());
        System.out.println("描述=" + order.getDes());

        // 2. order 加入一个鸡蛋
        order = new Egg(order);

        System.out.println("手抓饼 加入1个鸡蛋 费用 =" + order.cost());
        System.out.println("手抓饼 加入1个鸡蛋 描述 = " + order.getDes());

        // 3. order 加入一个鸡蛋

        order = new Egg(order);

        System.out.println("手抓饼 加入1个鸡蛋 加入2个鸡蛋 费用 =" + order.cost());
        System.out.println("手抓饼 加入1个鸡蛋 加入2个鸡蛋 描述 = " + order.getDes());

        // 3. order 加入一根烤肠

        order = new Sausage(order);

        System.out.println("手抓饼 加入1个鸡蛋 加入2个鸡蛋 加1根烤肠 费用 =" + order.cost());
        System.out.println("手抓饼 加入1个鸡蛋 加入2个鸡蛋 加1根烤肠 描述 = " + order.getDes());

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

        Snack order2 = new GrilledColdNoodles();

        System.out.println("考冷面 费用 =" + order2.cost());
        System.out.println("考冷面 描述 = " + order2.getDes());

        // 1. order2 加入一袋金针菇
        order2 = new NeedleMushroom(order2);
        System.out.println("考冷面 加入一袋金针菇  费用 =" + order2.cost());
        System.out.println("考冷面 加入一袋金针菇 描述 = " + order2.getDes());

    }
}

5、好嘞,您拿好

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

 前面总结了这么多,再从大神们的作品中找一个实际应用例子吧,毕竟那是经历实战检验的,肯定是有道理的。嗯,在平时的留意中我发现Java I/O系统的设计中用到了这一设计模式,因为Java I/O类库需要多种不同功能的组合。这里我就以InputStream为例简单说明一下,同样我们还是来看一下其类图:

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,是一致的,这里就不再赘述了。

 

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值