设计模式之装饰器模式 (十)

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


定义与类图来自百度百科。这个定义说到了装饰器模式的几个重点,不改变源文件,不使用继承,然后可以扩展一个对象的功能,这几个重点很好的介绍了装饰器模式。那么装饰器模式是怎么实现的呢,类图上Componet是一个被装饰的接口,ConcreteComponent是具体的被装饰的类,通过Decorator实现被装饰的接口,并持有一个被装饰接口的引用(组合的方式),通过调用持有的引用来实现被装饰的原方法,然后在具体装饰类ConcreteDecoratorA和B里去扩展新的方法,或是在原方法里面进行包装都是可以的。

IO的整体架构正是用到了装饰器模式,我在学习这个模式之后,再去看IO中类与类之间的关系,一下子就恍然大悟了,瞬间明白操作IO的时候为什么要封装来封装去。关于IO在最后会提到,这里先开始写一个装饰器模式。这里我举一个漫威的例子。

首先是被装饰的接口(人类),一个人类都有可能会有如下的几个行为。

package test;

public interface Human {

	void listen();

	void walk();

	void eat();

	void fly();

}

然后是被装饰的类(普通人类),实现接口。

package test;

public class NormalHuman implements Human {

	@Override
	public void listen() {
		System.out.println("人类普通的听觉");
	}

	@Override
	public void walk() {
		System.out.println("人类正常走路");
	}

	@Override
	public void eat() {
		System.out.println("正常吃饭");
	}

	@Override
	public void fly() {
		System.out.println("普通人不能飞");
	}
}

然后就是类图中的Decorator,算是一个中间类,(普通人进化成超能力者的中介)

package test;

public class Decorator implements Human {

	Human human;

	public Decorator(Human human) {
		this.human = human;
	}

	@Override
	public void listen() {
		human.listen();
	}

	@Override
	public void walk() {
		human.walk();
	}

	@Override
	public void eat() {
		human.eat();
	}

	@Override
	public void fly() {
		human.fly();
	}

}

然后是具体装饰的类(钢铁侠和蜘蛛侠)

package test;

public class ironMan extends Decorator {

	public ironMan(Human human) {
		super(human);
	}

	// 钢铁侠新增能力,飞行
	@Override
	public void fly() {
		System.out.println("钢铁侠的飞行");
	}

	@Override
	// 钢铁侠改良了正常人的跑步
	public void walk() {
		System.out.println("跑的飞快");
	}

}
package test;

public class spiderMan extends Decorator {

	public spiderMan(Human human) {
		super(human);
	}

	// 蜘蛛侠新增能力,吐丝
	public void Spinning() {
		System.out.println("蜘蛛侠吐丝");
	}

	@Override
	// 蜘蛛侠可以飞檐走壁,但走路还是和正常人一样走
	public void walk() {
		System.out.println("蜘蛛侠的飞檐走壁");
		super.walk();
	}

	@Override
	// 蜘蛛侠改进普通人的听觉
	public void listen() {
		System.out.println("蜘蛛侠敏锐的听觉");
	}

}

我们来试试普通人进化成超能力者的过程吧!

package test;

public class testA {

	public static void main(String[] args) {

		Human man = new NormalHuman();
		System.out.println("正常人:");
		man.walk();
		man.listen();
		man.eat();
		System.out.println("装饰成钢铁侠了:");
		ironMan Tony = new ironMan(man);
		Tony.fly();
		Tony.walk();
		Tony.listen(); // 但是钢铁侠也是人,听觉还是普通人的听觉(还是拥有被装饰类的方法)
		System.out.println("装饰成蜘蛛侠了:");
		spiderMan Parker = new spiderMan(man);
		Parker.Spinning();
		Parker.listen();
		Parker.walk();// 蜘蛛侠可以飞檐走壁,但走路还是普通人一样的走
		Parker.eat(); // 但是蜘蛛侠也是人,吃饭还是跟普通人一样(还是拥有被装饰类的方法)
		Parker.fly(); // 蜘蛛侠并没有飞行的功能,所以他和普通人一样不能飞行
		System.out.println("装饰成钢铁侠后再装饰成蜘蛛侠:");
		Parker = new spiderMan(new ironMan(man)); // 但在装饰成钢铁侠后再装饰成蜘蛛侠,蜘蛛侠也能飞
		Parker.fly();
		Parker.walk(); // 结合了钢铁侠和蜘蛛侠,现在蜘蛛侠除了飞檐走壁,跑的也飞快了!

	}
}

控制台打印


由上面的结果我们可以得出以下结论:

1. 可以扩展原先类没有的方法,比如钢铁侠有了普通人没有的飞行

2. 可以修改原先类的方法,比如钢铁侠走路跟普通人不一样,他跑的飞快

3. 装饰之后的类也还是可以拥有被装饰原先的方法,比如钢铁侠也是普通人,吃饭和普通一样

4. 多重装饰之后,本来蜘蛛侠不能飞,但装饰成钢铁侠,再装饰成的蜘蛛侠就可以拥有钢铁侠的方法(飞行),本来只有飞檐走壁和普通走路,多重装饰之后走路都飞快了!

相信这个例子应该可以让大家很好理解装饰器模式了,写这个类我也花了很久去思考,在这个思考的过程,我也更加深入的理解这个设计模式,所以我建议大家也可以动手写一写,把它写出来是促进你思考的过程。

开篇有提到IO也是用了装饰器模式,这里就粗略的讲一讲吧。先来介绍一下IO结构

InputStream (类似Human接口)截取部分。

package java.io;

public abstract class InputStream implements Closeable {

    // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
    // use when skipping.
    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);
    }
    //read方法,这里InputStream其实也用到了模板方法模式,具体的read()推迟到了子类去实现,而这里只需要提供大致骨架
    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 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(类似普通人类),实现了InputStream的方法,这里就不贴了

然后就是FilterInputStream (类似Decorator中间类),继承了InputStream,持有一个InputStream引用,实现所有InputStream的方法。与我举的例子一摸一样,这里也不贴了。

然后就是BufferedInputStream (类似钢铁侠蜘蛛侠),装饰的类,他继承了上面所说的中间类,并修改了read方法,并新增了一个reset方法(在InputStream中抛异常,没有功能)。

然后还是装饰的类DataInputStream,它扩展了原先的字节类,新增功能读Int,读Boolean。

接下来看具体实现吧

package test;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class testA {

	@SuppressWarnings({ "resource", "unused" })
	public static void main(String[] args) throws IOException {
		File file = new File("d:/test.txt");
		// 先写入一个值,后面数据流用到
		FileOutputStream fos = new FileOutputStream(file);
		DataOutputStream dos = new DataOutputStream(fos);
		dos.writeInt(123);

		FileInputStream fis = new FileInputStream(file); // 这里是普通人类
		int length = fis.available(); // 保存流长度
		System.out.println("普通流没有mark功能" + fis.markSupported());
		System.out.println("-----------------装饰成了缓存流----------------------");
		BufferedInputStream bis = new BufferedInputStream(fis); // 这里装饰成了缓存流
		System.out.println("缓存流有mark功能" + bis.markSupported());
		System.out.println("-----------------装饰成数据流----------------------");
		DataInputStream dis = new DataInputStream(fis);
		System.out.println("数据流还是没有mark功能" + dis.markSupported());
		System.out.println("-----------------装饰成缓存流后再装饰成数据流----------------------");
		dis = new DataInputStream(bis); // 这样,数据流又有读Int之类的方法又有mark,reset的方法
		System.out.println("数据流有mark功能了" + dis.markSupported());
		System.out.println(dis.readInt());
	}
}

控制台打印


这里的结果其实和我举的例子差不多,通过学习装饰器模式,应该可以对IO有了一个大致的理解了。

注:由于我的水平有限,有些地方说的可能有问题?欢迎大家指出,互相讨论互相学习进步!

猜你喜欢

转载自blog.csdn.net/qq_41737716/article/details/80550994