设计模式十二--装饰者模式

309 阅读6分钟

这是我参与更文挑战的第25天,活动详情查看: 更文挑战

设计模式

装饰者模式

何为装饰者模式?用通俗的话说就是给对象加上装饰,不改变它原本结构的同时让他动态的获得新的功能。装饰类和被装饰类可以独立发展,不会相互耦合。

举个例子:早晨上班吃早餐,公司门口有卖煎饼果子的,我们买的时候可以加肠、加蛋、加辣条,这里煎饼果子就是被装饰类,而附加的肠、蛋、辣条就是装饰类。

现在我们早餐店有煎饼果子(5元)、肉夹馍(6元)、鸡蛋灌饼(7元),而这些都可以选择加肠(2元)、加蛋(1元)、加辣条(0.5元)让它更加美味,那么组合之后的价格是多少呢?

如果使用传统的方式,组合的种类太多,导致类的递增,从而提高项目的维护性。

首先我们将煎饼果子、肉夹馍、鸡蛋灌饼抽象成Eat对象,并且有两个属性des和price,一个方法cost供子类继承

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public abstract class Eat {
    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();
}

并且加了缓冲层,将具体类(煎饼果子、肉夹馍、鸡蛋灌饼)的共同点抽取出来

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Food extends Eat {
    @Override
    public float cost() {
        return super.getPrice();
    }
}

煎饼果子Pancake

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Pancake extends Food {
    public Pancake() {
        setDes("煎饼果子");
        setPrice(5.0f);
    }
}

肉夹馍

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class ChineseHamburger extends Food {
    public ChineseHamburger() {
        setDes("肉夹馍");
        setPrice(6.0f);
    }
}

然后就是装饰者Decorator继承并组合被装饰者Eat,这里的cost就可以递归的计算出组合之后商品的价格。

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Decorator extends Eat {
    private Eat eat;

    public Decorator(Eat eat) {
        this.eat = eat;
    }

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

    @Override
    public String getDes() {
        return super.des + "(" + super.getPrice() + ")加" + eat.getDes();
    }
}

肠Sausage

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Sausage extends Decorator {
    public Sausage(Eat eat) {
        super(eat);
        setDes("加香肠");
        setPrice(2.0f);
    }
}

辣条SpicyStrips

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class SpicyStrips extends Decorator {
    public SpicyStrips(Eat eat) {
        super(eat);
        setDes("加辣条");
        setPrice(0.5f);
    }
}

鸡蛋Egg

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Egg extends Decorator {
    public Egg(Eat eat) {
        super(eat);
        setDes("加鸡蛋");
        setPrice(1.0f);
    }
}

main

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Buy {
    public static void main(String[] args) {
        Eat eat = new Pancake();
        System.out.println(eat.getDes() + ":" + eat.cost());
        eat = new Egg(eat);
        System.out.println(eat.getDes() + "的价格:" + eat.cost());
        eat = new SpicyStrips(eat);
        System.out.println(eat.getDes() + "的价格:" + eat.cost());
        eat = new Sausage(eat);
        System.out.println(eat.getDes() + "的价格:" + eat.cost());
    }
}

可以轻松的实现我们想要的效果。而且当我们增加新的食品的时候,也有良好的扩展性。当我们增加鸡蛋灌饼,

只需要增加EggCake

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class EggCake extends Food {
    public EggCake() {
        setDes("鸡蛋灌饼");
        setPrice(7.0f);
    }
}

main

package com.wangscaler.decorator;

/**
 * @author wangscaler
 * @date 2021.06.28 10:28
 */
public class Buy {
    public static void main(String[] args) {
        Eat eat = new EggCake();
        System.out.println(eat.getDes() + ":" + eat.cost());
        eat = new Egg(eat);
        System.out.println(eat.getDes() + "的价格:" + eat.cost());
        eat = new SpicyStrips(eat);
        System.out.println(eat.getDes() + "的价格:" + eat.cost());
        eat = new Sausage(eat);
        System.out.println(eat.getDes() + "的价格:" + eat.cost());
    }
}

轻松实现。

那么装饰者Decorator是怎么递归实现价格的计算的呢?

1、看我们的main函数,起初我们new EggCake();,此时EggCake通过继承Food,而Food继承Eat,通过父类的Eat的setDes和setPrice将描述和价格写入,而此时Decorator也继承了Eat,所以在Decorator的cost()方法和getDes()方法中 super.dessuper.getPrice()拿到的其实是EggCake的描述和价格,也就是加鸡蛋1.0f

2、main函数new Egg(eat);中调用的Egg类的super(eat);也就是Decorator的eat,此时通过构造器public Decorator(Eat eat) {this.eat = eat;}

将EggCake的eat赋值给组合的对象eat,所以Decorator中的eat.cost();就是上一步EggCake中eat.cost()已经计算完的结果也就是7.0f。

3、所以Decorator中的cost执行完之后,将返回的值为1.0f+7.0f为8.0f。

4、下述代码重复以上操作。

源码中的装饰者模式

JDK中的FilterInputStream

抽象类InputStream类似于上述的Eat

package java.io;
public abstract class InputStream implements Closeable {
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;

    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }

    public int available() throws IOException {
        return 0;
    }


    public void close() throws IOException {}

    public synchronized void mark(int readlimit) {}


    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    public boolean markSupported() {
        return false;
    }

}

具体类有很多。FileInputStream、ByteArrayInputStream等等,类似于我们上述的Pancake、ChineseHamburger、EggCake这里以FileInputStream为例。

package java.io;

import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;

public
class FileInputStream extends InputStream
{
    private final FileDescriptor fd;

    private final String path;

    private FileChannel channel = null;

    private final Object closeLock = new Object();
    private volatile boolean closed = false;

    public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }

    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }

    public FileInputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;

 
        fd.attach(this);
    }

    private native void open0(String name) throws FileNotFoundException;

    private void open(String name) throws FileNotFoundException {
        open0(name);
    }


    public int read() throws IOException {
        return read0();
    }

    private native int read0() throws IOException;

    private native int readBytes(byte b[], int off, int len) throws IOException;

    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }

 
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }

    public native long skip(long n) throws IOException;

    public native int available() throws IOException;

    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

    public final FileDescriptor getFD() throws IOException {
        if (fd != null) {
            return fd;
        }
        throw new IOException();
    }


    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, false, this);
            }
            return channel;
        }
    }

    private static native void initIDs();

    private native void close0() throws IOException;

    static {
        initIDs();
    }

      java.io.FileInputStream#close()

    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }
}

FilterInputStream类似于上述的Decorator,在这里继承并组合了我们的抽象类InputStream

package java.io;

public
class FilterInputStream extends InputStream {
  
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }

   
    public long skip(long n) throws IOException {
        return in.skip(n);
    }

  
    public int available() throws IOException {
        return in.available();
    }

    public void close() throws IOException {
        in.close();
    }

   
    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }

  
    public synchronized void reset() throws IOException {
        in.reset();
    }

    public boolean markSupported() {
        return in.markSupported();
    }
}

而BufferedInputStream、DataInputStream等等就相当于上述的Egg、Sausage、SpicyStrips,这里以BufferedInputStream为例

package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public
class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

    protected volatile byte buf[];

    private static final
        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
        AtomicReferenceFieldUpdater.newUpdater
        (BufferedInputStream.class,  byte[].class, "buf");

    protected int count;

    protected int pos;

    protected int markpos = -1;

    protected int marklimit;

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

    private byte[] getBufIfOpen() throws IOException {
        byte[] buffer = buf;
        if (buffer == null)
            throw new IOException("Stream closed");
        return buffer;
    }
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

    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)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

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

    private int read1(byte[] b, int off, int len) throws IOException {
        int avail = count - pos;
        if (avail <= 0) {
            /* If the requested length is at least as large as the buffer, and
               if there is no mark/reset activity, do not bother to copy the
               bytes into the local buffer.  In this way buffered streams will
               cascade harmlessly. */
            if (len >= getBufIfOpen().length && markpos < 0) {
                return getInIfOpen().read(b, off, len);
            }
            fill();
            avail = count - pos;
            if (avail <= 0) return -1;
        }
        int cnt = (avail < len) ? avail : len;
        System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
        pos += cnt;
        return cnt;
    }

    public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        getBufIfOpen(); // Check for closed stream
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int n = 0;
        for (;;) {
            int nread = read1(b, off + n, len - n);
            if (nread <= 0)
                return (n == 0) ? nread : n;
            n += nread;
            if (n >= len)
                return n;
            // if not closed but no bytes available, return
            InputStream input = in;
            if (input != null && input.available() <= 0)
                return n;
        }
    }

    public synchronized long skip(long n) throws IOException {
        getBufIfOpen(); // Check for closed stream
        if (n <= 0) {
            return 0;
        }
        long avail = count - pos;

        if (avail <= 0) {
            // If no mark position set then don't keep in buffer
            if (markpos <0)
                return getInIfOpen().skip(n);

            // Fill in buffer to save bytes for reset
            fill();
            avail = count - pos;
            if (avail <= 0)
                return 0;
        }

        long skipped = (avail < n) ? avail : n;
        pos += skipped;
        return skipped;
    }

    public synchronized int available() throws IOException {
        int n = count - pos;
        int avail = getInIfOpen().available();
        return n > (Integer.MAX_VALUE - avail)
                    ? Integer.MAX_VALUE
                    : n + avail;
    }

    public synchronized void mark(int readlimit) {
        marklimit = readlimit;
        markpos = pos;
    }

    public synchronized void reset() throws IOException {
        getBufIfOpen(); // Cause exception if closed
        if (markpos < 0)
            throw new IOException("Resetting to invalid mark");
        pos = markpos;
    }

    public boolean markSupported() {
        return true;
    }


    public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();
                return;
            }
            // Else retry in case a new buf was CASed in fill()
        }
    }
}

从以上可以看出,这个FilterInputStream就是装饰者模式。

总结

1、装饰者模式就是动态的将新功能附加到对象身上,比如我们例子中的加肠、加蛋。

2、也能体现开闭原则,比如我们增加的EggCake。

3、装饰类和被装饰类可以独立发展,而不会相互耦合

4、 使用场景:扩展一个类的功能或者动态增加功能即插即用,动态撤销。

装饰器模式主要包含以下角色。

  1. 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象,即上述的Eat。
  2. 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责,即上述的EggCake等。
  3. 抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能,即上述的Decorator。
  4. 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任,及上述的Egg等。

参考资料