概念
装饰器模式指的是在不必改变原类文件和不使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
一般情况下想要扩展一个类的功能需要使用继承来实现,比如说HashSet有保证元素不重复的特点,但是它是无序存储,即存的顺序和取出的顺序不一致,为了让他在不修改原来对象的情况下实现有序存储,Java类库里面实现了一个继承HashSet类的子类LinkedHashSet。
但是如果想要拓展的这个类有许多子类,那么有多少个子类就要创建多少个新的类,这会使程序变得十分复杂。
使用装饰器模式就可以解决这个问题。使用装饰器模式扩展一个类的功能我们只需要用装饰类来包裹被扩展的类。
代码演示
下面我们用代码模拟装饰器模式。
首先我们先创建一个笔记本电脑类。
//定义了一个笔记本电脑的抽象类
public abstract class Laptop {
protected String name;
protected int price;
public abstract String getName();
public abstract int getPrice();
}
我们创建一个联想电脑类和华硕电脑类,这两个类就是要被装饰的类。
public class Lenovo extends Laptop{
public Lenovo(){
this.name = "联想电脑";
this.price = 5000;
}
@Override
public String getName() {
return name;
}
@Override
public int getPrice() {
return price;
}
}
public class ASUS extends Laptop {
public ASUS() {
this.name = "华硕";
this.price = 5500;
}
@Override
public String getName() {
return name;
}
@Override
public int getPrice() {
return price;
}
}
创建一个装饰类基类,主要这里有一个要点,装饰类和被装饰类一定要有相同的父类或者间接父类,后面会解释原因。
//电脑配件的基类
public abstract class AddParts extends Laptop{
//这里必须创建被装饰类的实例变量,只有这样才能做到装饰类包裹(装饰)被装饰类。
Laptop laptop;
public abstract String getName();
public abstract int getPrice();
}
在这里我们创建两个装饰类:添加显示器和添加键盘。
public class AddMonitor extends AddParts {
public AddMonitor(Laptop laptop) {
this.laptop = laptop;
}
@Override
public String getName() {
return laptop.getName() + " "+"显示器";
}
@Override
public int getPrice() {
return laptop.getPrice()+1000;
}
}
public class AddKeyBoard extends AddParts {
public AddKeyBoard(Laptop laptop) {
this.laptop = laptop;
}
@Override
public String getName() {
return laptop.getName() +" "+"键盘";
}
@Override
public int getPrice() {
return laptop.getPrice() + 200;
}
}
接下来我们进行测试
public class Test {
public static void main(String[] args) {
//一台普通联想电脑
Lenovo lenovo = new Lenovo();
System.out.println(lenovo.getName() + " "+lenovo.getPrice());
//一台普通华硕电脑
ASUS asus = new ASUS();
System.out.println(asus.getName() + " " +asus.getPrice());
//一台装了键盘的联想电脑
AddKeyBoard lenovo2 = new AddKeyBoard(new Lenovo());
System.out.println(lenovo2.getName()+" "+lenovo2.getPrice());
//一台装了显示器和键盘的华硕电脑
AddKeyBoard asus2 = new AddKeyBoard(new AddMonitor(new ASUS()));
System.out.println(asus2.getName() + " "+ asus2.getPrice());
}
}
运行结果
通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。如果不使用装饰器模式,又不想改变原有的类,使用继承来拓展类的功能,那么一种组合方式就对应一个新的类。
前面我们还提到一个要点:装饰类和被装饰类一定要有相同的父类或者间接父类。
因为只有这样才能让被装饰类被多个装饰类装饰,即让电脑同时装上显示器和键盘。
模式特点
这里我们总结一下装饰器模式的特点:
- 装饰类和被装饰类一定要有相同的父类或者间接父类。
- 装饰类包含一个真实对象的引用(reference),如下图所示。
- 在不改变原先类的情况下,通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
设计原则
- 多组合,少继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。 - 类应设计的对扩展开放,对修改关闭。
设计原则来自于百度百科。
装饰器模式的适用性
- 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
Java-IO中的装饰器模式
在上面的例子中,我们是用AddParts作为装饰类的基类来增强Laptop的功能,在Java中,FilterInputStream就是用来增强InputStream的。
下面我们以FilterInputStream和BufferedInputStream为例讲解。
FilterInputStream作为装饰器加强了BufferedInputStream,下面是他们的部分源代码,加上了我的注释。
下面是被装饰类,相当于例子中的 Lenovo和ASUS。
public class FileInputStream extends InputStream{
}
下面是装饰器的基类,相当于AddParts,它与FileInputStream有着共同父类,类中有该父类的引用。
public class FilterInputStream extends InputStream {
//装饰类包含一个真实对象的引用(reference)
protected volatile InputStream in;
}
下面是装饰类,继承了装饰器基类,相当于例子中的加键盘和加显示器。
public class BufferedInputStream extends FilterInputStream {
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];
}
}
下面是对BufferedInputStream的使用。
public class Test {
public static void main(String[] args) throws FileNotFoundException {
BufferedInputStream bfs = new BufferedInputStream(new FileInputStream("text.txt"));
}
}
学习心得
这是我第一次接触Java中的设计模式,刚开始看的时候感觉很难理解,当自己动手去模拟实现这个过程的时候,才慢慢体会到这种设计模式的巧妙,所以编程语言的学习还是多多动手,下面是我学习过程中参考的资料。
JavaGuide------装饰器模式