Java设计模式-装饰模式

来继续之前的设计模式之旅~~这次介绍的是装饰(包装)模式,还是使用 Java 来进行说明,首先来简单了解下装饰模式:

装饰模式又名包装( Wrapper )模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

Wiki 上的解释为:

是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,装饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

介绍

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

装饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。

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

然而大多数的装饰模式实际上是半透明的装饰模式(介于装饰模式和适配器模式之间的),这样的装饰模式也称做半装饰、半适配器模式。

使用前

使用场景一般是:

  • 在不影响其他对象的情况下,以动态,透明的方式给单个对象添加职责。
  • 处理那些可以撤销的职责。
  • 当不能采用生成子类的方法进行扩充时。

优点

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
    装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。
    继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点

使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
额,所以说命名很关键

简单示例

在装饰模式中的角色有:

  • 抽象构件(Component)角色:
    给出一个抽象接口,以规范准备接收附加责任的对象。
    所有的装饰者和被装饰对象都要继承它(如果是接口就是实现)
  • 具体构件(ConcreteComponent)角色:
    定义一个将要接收附加责任的类(将要被装饰的类)。
    继承上面的抽象构建
  • 装饰(Decorator)角色:
    持有一个构件( Component )对象的实例,并定义一个与抽象构件接口一致的接口。
    也可以是一个实现 interface 的抽象类,也就是说要继承(广义上的)抽象构件
  • 具体装饰(ConcreteDecorator)角色:
    负责给构件对象“贴上”附加的责任。
    继承自装饰角色(抽象);在内部维护一个被装饰对象

下面用代码来说明,先来 抽象构件角色,所有的装饰者、被装饰对象都要实现这个接口:

1
2
3
4
public interface Loli {
String speak(String name);
void hug();
}

然后是具体构件角色,也就是将要被装饰的具体对象,这解释的是啥玩意,说白了就是被装饰对象呗
这里我就写一个了:

1
2
3
4
5
6
7
8
9
10
11
public class LegitimateLoli implements Loli {
@Override
public String speak(String name) {
return "( ̄^ ̄)" + name;
}

@Override
public void hug() {
System.out.println("(づ。◕‿‿◕。)づ");
}
}

装饰角色(引入构件类, 给具体构件类增加职责,但是具体职责在其子类中实现),一般是写成抽象的,当然也可以下面的这样写法吧,大概….:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Decorator implements Loli {
// 持有的构件实例
private Loli mLoli;

public Decorator(Loli loli) {
mLoli = loli;
}

@Override
public String speak(String name) {
// 这里具体实现直接委派给构件实例了...
return mLoli.speak(name);
}

@Override
public void hug() {
// 同上
mLoli.hug();
}
}

具体装饰角色(主要就是对功能进行扩展),嗯,就是装饰者了,大部分会在这里维护被装饰对象,不过也看到过在装饰角色中直接就弄好的,比如说现在的这个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LovelyLoli extends Decorator {

public LovelyLoli(Loli loli) {
super(loli);
}

@Override
public String speak(String name) {
return super.speak(name) + " +++装饰+++ 我很可爱~~";
}

@Override
public void hug() {
// 执行原来的构件的功能
super.hug();

// 进行扩展功能
// TODO
System.out.println("+++装饰+++ 我很可爱~~");
}
}

最后就是来调用测试啦,怎么感觉好羞耻(:捂脸..

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainTest {
public static void main(String[] args) {
// 选择了标准 Loli
Loli loli = new LegitimateLoli();

//对标准 Loli 进行了装饰(增加功能)
loli = new LovelyLoli(loli);

loli.hug();
System.out.println("----------");
System.out.println(loli.speak("bfchengnuo"));
}
}

嗯,大体思想就是这样啦~~
当然也不一定都是这样的写法,比如可以把构件实例放在具体装饰角色里面,怎么喜欢怎么来吧~~
还是要看实际情况嘛~~

小小总结

  • 装饰者和被装饰对象有相同的超类
  • 你可以用一个或者多个装饰者包装一个对象
  • 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合可以用装饰过的对象代替他
  • 装饰者可以在所委托的被装饰者的行为之前或者之后,加上自己的行为,已达到特定的目的
  • 对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的用你喜欢的装饰者来装饰对象

在 Java 的 I/O 实现中就使用了这个模式,最原始的 InputStream 是抽象的,相当于是抽象组件;FileInputStream 继承自它,相当于是具体构件,也就是被装饰对象的角色;
我们在使用的时候大多使用 BufferedInputStream 进行装饰,它继承自 FilterInputStream ,它是一个抽象装饰者,或者在外面再套一层…..

透明与半透明

装饰模式对客户端的透明性要求程序不要声明一个 ConcreteComponent 类型的变量,而应当声明一个 Component 类型的变量。
上面这句标准解释反正我看不懂,按照上面的栗子就是,这是透明的,是正确的:

1
2
Loli loli = new LegitimateLoli();
loli = new LovelyLoli(loli);

这样干是不对的,不透明的:

1
2
Loli loli = new LegitimateLoli();
LovelyLoli loli2 = new LovelyLoli(loli);

前面也说过,纯粹的装饰模式是很难找到的,按照透明模式来,我如果在 LovelyLoli 里面进行扩展了方法是调用不到的,这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。

换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明 ConcreteDecorator 类型的变量,从而可以调用 ConcreteDecorator 类中才有的方法,说白了就是类似上面说的错误做法。

半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。

为什么是半透明?
也就是说,对于客户端而言,具体构件类型无须关心,是透明的;但是具体装饰类型必须指定,这是不透明的

扩充资料

如果不是很明白可以看:
http://www.cnblogs.com/java-my-life/archive/2012/04/20/2455726.html
http://blog.csdn.net/zhshulin/article/details/38665187
http://blog.csdn.net/lovelion/article/details/7425873

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~