装饰模式

装饰模式是一种设计模式,允许在不修改原有对象的情况下动态添加功能。它通过创建包装对象来包裹真实对象,提供了类继承的另一种选择。在运行时,装饰模式可以按需组合功能,避免了大量子类的产生。Java I/O系统是装饰模式的一个经典应用,如InputStream和其装饰类如BufferedInputStream。这种模式适用于需要扩展类功能,或者动态添加和撤销功能的场景。

装饰模式23种设计模式之一,英文名叫Decorator Pattern,又叫装饰者模式

装饰模式可以在不必改变原类文件和不使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。通过使用装饰模式,可以在运行时扩充一个类的功能。

原理是:增加一个装饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为装饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。装饰类必须和原来的类有相同的接口。


装饰模式是类继承的另外一种选择。

装饰模式与类继承的区别:类继承在编译时候增加行为,而装饰模式是在运行时增加行为。

当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味著要为每一种组合创建一个新类。相反,装饰模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。一个装饰模式的示例是JAVA里的Java I/O的实现。


装饰模式的UML如下:


装饰模式共涉及4个角色:

抽象构建角色(Component):该角色用于抽象需要装饰的对象,也就是原始对象。

具体构建对象(ConcreteComponent):该角色需要实现抽象构建角色的接口,是具体被装饰的原始对象,有自己的原始逻辑实现。

装饰角色(Decorator):该角色持有一个构建对象的实例,并定义一个与抽象构建角色一样的接口。

具体装饰角色(ConcreteDecorator):该角色用来装饰构建角色,是我们需要添加的装饰。


上述的角色代码实现如下:

抽象构建角色Component

public interface Component {

	public void baseMethod();
	
}

具体构建角色ConcreteComponent

public class ConcreteComponent implements Component {

	@Override
	public void baseMethod() {
		// TODO Auto-generated method stub
		//sth to do...
		System.out.println("It is the basic action...");
	}
	
}

装饰角色Decorator:

public class Decorator implements Component {

	private Component component;
	
	public Decorator(Component component) {
		this.component = component;
	}
	
	@Override
	public void baseMethod() {
		component.baseMethod();
	}
	
}

具体装饰角色ConcreteDecorator

public class ConcreteDecorator extends Decorator {

	public ConcreteDecorator(Component component) {
		super(component);
	}
	
	private void decorateMethod() {
		//sth you want add on component
		System.out.println("Add something to decotate it...");
	}
	
	@Override
	public void baseMethod() {
		super.baseMethod();
		decorateMethod();
	}
	
}

以上就是装饰模式的4种角色,客户端调用的时候,只需要把构建角色(原始角色)作为参数传进装饰角色中去

客户调用:

public class Client {

	public static void main(String[] args) {
		Component component = new ConcreteComponent();
		component = new ConcreteDecorator(component);
		component.baseMethod();
	}
}

这些或许还看不出装饰模式的一些优点,比如上述过程完全可以继承来实现,而且过程还简单。这里再给出装饰模式的一个具体应用


一个装饰模式的具体应用

场景:现有一个蛋糕类,实际使用(吃)的时候,我们可能需要根据客户的需求向蛋糕Cake上加上各种各样的点心甜点(樱桃、巧克力、奶酪...)进行装饰,而且根据实际情况我们也不知道顾客的具体需求搭配,如果使用继承就相当于做出各种搭配的蛋糕,如果只有(樱桃、巧克力、奶酪)三种配料,那么使用继承产生的子类就可能有以下这些:

  • 樱桃蛋糕
  • 巧克力蛋糕
  • 奶酪蛋糕
  • 樱桃+巧克力蛋糕
  • 樱桃+奶酪蛋糕
  • ...

根据组合,一共应该有7种搭配吗,这还只是3种配料点心的情况下,如果按照实际中,几十种点心,那么产生的子类将是泛滥的。

因此我们考虑使用装饰模式去解决,先只是做好构建对象(原始对象)蛋糕,在做好各种装饰对象点心,然后客户端调用的时候只需要自行添加装饰就可以了。


具体的UML如下:



代码实现如下:

抽象构建角色Cake,有一个description描述方法,可能的种类有甜蛋糕、咸蛋糕、酸蛋糕...

public interface Cake {

	public void description();
	
}


具体的构建角色SweetCake:

public class SweetCake implements Cake {

	@Override
	public void description() {
		System.out.println("这是甜味蛋糕");
	}
	
}

当然这样的甜蛋糕是不美味的,因为只是只甜味面包,我们需要加上各种奶油和水果

装饰角色Dessert:

public class Dessert implements Cake {

	private Cake cake;
	
	public Dessert(Cake cake) {
		this.cake = cake;
	}
	
	@Override
	public void description() {
		cake.description();
	}
}

具体的装饰角色,这里给出了三种点心装饰Cherry、Chocolate、Cheese

public class Cherry extends Dessert {

	public Cherry(Cake cake) {
		super(cake);
	}
	
	public void add() {
		System.out.println("已经加了【樱桃】搭配");
	}
	
	@Override
	public void description() {
		super.description();
		add();
	}
	
}

public class Chocolate extends Dessert {

	public Chocolate(Cake cake) {
		super(cake);
	}
	
	public void add() {
		System.out.println("已经加了【巧克力】搭配");
	}
	
	@Override
	public void description() {
		super.description();
		add();
	}
	
}

public class Cheese extends Dessert {

	public Cheese(Cake cake) {
		super(cake);
	}
	
	public void add() {
		System.out.println("已经加了【奶酪】搭配");
	}
	
	@Override
	public void description() {
		super.description();
		add();
	}
	
}

以上使用装饰模式的蛋糕制作就完成了

当客户端调用时,可以根据需求自行搭配

public class Client {

	public static void main(String[] args) {
		Cake cake = new SweetCake();
		
		cake = new Chocolate(cake);
		cake = new Cherry(cake);
		cake = new Cheese(cake);
		
		cake.description();
	}
	
}

或者

public class Client {

	public static void main(String[] args) {
		Cake cake = new SweetCake();
		
		cake = new Chocolate(cake);
		cake = new Cheese(cake);
		
		cake.description();
	}
	
}

或者...

以上可以总结出装饰模式的一些优点:

  • 装饰类和被装饰类可以独立发展,而不会相互耦合。即Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator类也不用直到具体的构建。
  • 装饰模式是继承关系的一种替代方案。装饰类Decorator不管装饰多少层,返回的对象还是Component,而且装饰模式可以防止子类的泛滥。
  • 装饰模式可以动态的扩展类的功能,而子类继承只能是静态的扩展类的功能

当然也有一些缺点:

  • 多层装饰是比较复杂的
  • 会产生更多的的对象(可以发现上述客户调用使用了大量的new)


一些使用装饰模式的场景:

  • 需要扩展一个类的功能,或者给一个类增加附加功能;
  • 需要动态的给一个类添加功能,这些功能还可以动态的撤销;
  • 需要为一批类进行改装或者加装功能(比如,上述还可能有咸蛋糕、酸蛋糕原始对象)


装饰模式的一个典型使用就是Java中的输入输出流

这里只列出输入流来进行说明:


这里

抽象构建角色(Component)是InputStream,查看源码可以发现InputStream是一个抽象类,包含了核心抽象方法read(),没有任何实现,它只是规范原始对象。

具体构建角色(Concrete Component):这里是ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream,它们实现了InputStream定义的抽象方法read()

抽象装饰角色(Decorator):这里是FilterInputStream,它继承了InputStream,含有一个InputStream类的实例,但其中的核心read方法没有自己实现,而是直接引用了InputStream的read()方法。

具体装饰角色(Concrete Decorator):这是是下排的BufferedInputStream等,它们继承了FilterInputStream,实现了read()方法,创建BufferedInputStream时,需要把一个Component对象,也就是InputStream对象传进它的构造方法。


我们通过源码来验证上述观点:

InputStream只是一个抽象类,核心的read()方法没有提高实现,只是定义了原始对象的规范,是原始对象的抽象,和我们的Cake类是相似的,只是我们的Cake应用是使用接口,差别不大。




具体构建角色ByteArrayInputStream相当于上述的SweetCake,是一个原始对象,但是实现了InputStream定义的抽象read()方法

/**
 * A <code>ByteArrayInputStream</code> contains
 * an internal buffer that contains bytes that
 * may be read from the stream. An internal
 * counter keeps track of the next byte to
 * be supplied by the <code>read</code> method.
 * <p>
 * Closing a <tt>ByteArrayInputStream</tt> has no effect. The methods in
 * this class can be called after the stream has been closed without
 * generating an <tt>IOException</tt>.
 *
 * @author  Arthur van Hoff
 * @see     java.io.StringBufferInputStream
 * @since   JDK1.0
 */
public
class ByteArrayInputStream extends InputStream {

...

 /**
     * Reads the next byte of data from this input stream. The value
     * byte is returned as an <code>int</code> in the range
     * <code>0</code> to <code>255</code>. If no byte is available
     * because the end of the stream has been reached, the value
     * <code>-1</code> is returned.
     * <p>
     * This <code>read</code> method
     * cannot block.
     *
     * @return  the next byte of data, or <code>-1</code> if the end of the
     *          stream has been reached.
     */
    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

    /**
     * Reads up to <code>len</code> bytes of data into an array of bytes
     * from this input stream.
     * If <code>pos</code> equals <code>count</code>,
     * then <code>-1</code> is returned to indicate
     * end of file. Otherwise, the  number <code>k</code>
     * of bytes read is equal to the smaller of
     * <code>len</code> and <code>count-pos</code>.
     * If <code>k</code> is positive, then bytes
     * <code>buf[pos]</code> through <code>buf[pos+k-1]</code>
     * are copied into <code>b[off]</code>  through
     * <code>b[off+k-1]</code> in the manner performed
     * by <code>System.arraycopy</code>. The
     * value <code>k</code> is added into <code>pos</code>
     * and <code>k</code> is returned.
     * <p>
     * This <code>read</code> method cannot block.
     *
     * @param   b     the buffer into which the data is read.
     * @param   off   the start offset in the destination array <code>b</code>
     * @param   len   the maximum number of bytes read.
     * @return  the total number of bytes read into the buffer, or
     *          <code>-1</code> if there is no more data because the end of
     *          the stream has been reached.
     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
     * <code>len</code> is negative, or <code>len</code> is greater than
     * <code>b.length - off</code>
     */
    public synchronized int read(byte b[], int off, int len) {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        }

        if (pos >= count) {
            return -1;
        }

        int avail = count - pos;
        if (len > avail) {
            len = avail;
        }
        if (len <= 0) {
            return 0;
        }
        System.arraycopy(buf, pos, b, off, len);
        pos += len;
        return len;
    }

再看同样继承InputStream的 FilterInputStream对象,它是一个装饰角色,相当于上述的点心类(Dessert),包含一个Component类的引用,没有实现read()方法,而是直接调用原始类的方法。

/**
 * A <code>FilterInputStream</code> contains
 * some other input stream, which it uses as
 * its  basic source of data, possibly transforming
 * the data along the way or providing  additional
 * functionality. The class <code>FilterInputStream</code>
 * itself simply overrides all  methods of
 * <code>InputStream</code> with versions that
 * pass all requests to the contained  input
 * stream. Subclasses of <code>FilterInputStream</code>
 * may further override some of  these methods
 * and may also provide additional methods
 * and fields.
 *
 * @author  Jonathan Payne
 * @since   JDK1.0
 */
public
class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;  //包含Component的一个实例

    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param   in   the underlying input stream, or <code>null</code> if
     *          this instance is to be created without an underlying stream.
     */
    protected FilterInputStream(InputStream in) {  //通过构造方法传入
        this.in = in;
    }

    /**
     * Reads the next byte of data from this input stream. The value
     * byte is returned as an <code>int</code> in the range
     * <code>0</code> to <code>255</code>. If no byte is available
     * because the end of the stream has been reached, the value
     * <code>-1</code> is returned. This method blocks until input data
     * is available, the end of the stream is detected, or an exception
     * is thrown.
     * <p>
     * This method
     * simply performs <code>in.read()</code> and returns the result.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterInputStream#in
     */
    public int read() throws IOException {  //没有自己实现read()方法,直接调用Component的原始方法,可以比较Dessert类
        return in.read();
    }

再看具体的装饰角色BufferedInputStream等,它实现了抽象装饰角色,需要在构造方法中传一个原始对象给抽象装饰角色,相当于上述Cherry等装饰点心,可以类比之。

public
class BufferedInputStream extends FilterInputStream {

...

 /**
     * Creates a <code>BufferedInputStream</code>
     * with the specified buffer size,
     * and saves its  argument, the input stream
     * <code>in</code>, for later use.  An internal
     * buffer array of length  <code>size</code>
     * is created and stored in <code>buf</code>.
     *
     * @param   in     the underlying input stream.
     * @param   size   the buffer size.
     * @exception IllegalArgumentException if size <= 0.
     */
    public BufferedInputStream(InputStream in, int size) {
        super(in); //传原始对象给父类抽象装饰角色
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
实现了read()方法

 /**
     * See
     * the general contract of the <code>read</code>
     * method of <code>InputStream</code>.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if this input stream has been closed by
     *                          invoking its {@link #close()} method,
     *                          or an I/O error occurs.
     * @see        java.io.FilterInputStream#in
     */
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

其他的输入流类也是一样,可以参照源码,类比之前的Cake蛋糕应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值