裝飾模式和它在JDK中的實現

對裝飾者模式的一個通俗的理解就是:一個東西A包裝了另外一個東西B,A在B的功能基礎上又擴展了新的功能,但是對外提供的接口不變

裝飾者模式(Decorator)的定義:
動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更爲靈活

通過使用裝飾模式,可以在運行時擴充一個類的功能。原理是:增加一個裝飾類包裹原來的類,包裹的方式一般是通過在將原來的對象作爲裝飾類的構造函數的參數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接調用原來的類中的方法。裝飾類必須和原來的類有相同的接口。

裝飾模式是類繼承的另外一種選擇。類繼承在編譯時候增加行爲,而裝飾模式是在運行時增加行爲。

當有幾個相互獨立的功能需要擴充時,這個區別就變得很重要。在有些面向對象的編程語言中,類不能在運行時被創建,通常在設計的時候也不能預測到有哪幾種功能組合。這就意味着要爲每一種組合創建一個新類。相反,裝飾模式是面向運行時候的對象實例的,這樣就可以在運行時根據需要進行組合。

裝飾模式的類圖:

裝飾模式的實現:

public interface Component {
    void doSomething();
}    


public class ConcreteComponent implements Component {
    @Override
    public void doSomething() {
        System.out.println("被裝飾的原始類的代碼");

    }
}


public class Decorator implements Component {
    private Component component;

    @Override
    public void doSomething() {
       // component.doSomething();
        //do nothing  需要在實現類來進行覆蓋
    }

    public void setComponent(Component component) {
        this.component = component;
    }

    protected Component getComponent() {
        return component;
    }
}

public class PrintLineDecorator extends Decorator {
    public PrintLineDecorator(Component component) {
        setComponent(component);
    }

    @Override
    public void doSomething() {
        System.out.println("在真正的調用之前打印一行");
        getComponent().doSomething();
    }
}


public class ComponentClient {
    public static void main(String[] args) {
        Component concreteComponent = new ConcreteComponent();
        PrintLineDecorator printLineDecorator = new PrintLineDecorator(concreteComponent);
        printLineDecorator.doSomething();
    }
}

JDK中的裝飾模式的分析

JDK中的BufferedInputStream和BufferedOoutputStream是典型的裝飾者模式的實現

FilterInputStream是裝飾者的父類,同時FilterInputStream中有一個InputStream類型的對象,指向實際需要被裝飾的類,在子類中(BufferedInputStream等),在調用實際被裝飾的類之前,或者之後,可以增加新的處理邏輯,我們看看BufferedInputStream是怎麼做的

read()方法:

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

fill方法:

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;
}

整體的邏輯就是:讀取的時候,先調用fill()將數據填充到buffer裏,再將buffer裏的數據返回,將數據填充到buffer的這個過程中,調用了原始類的read()方法到buffer

getInIfOpen().read(buffer, pos, buffer.length - pos);

爲原本沒有緩衝功能功的inputStream增加緩衝的功能,一次讀取更多的數據,減少了讀取的次數,提升了性能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章