下面是我们实现的SecureMessageWriter和EncryptedMessageWriter。
private string message;
//被装饰者
private IMessageWriter messageWriter;
public SecureMessageWriter(IMessageWriter msgWriter) {
this.messageWriter = msgWriter;
}
public string Message {
set { message = value; }
}
public void WriteMessage(string filePath) {
if (this.ValidateUser()) {//添加新的行为
//正如你所见,在调用被装饰者的标准方法前我们添加了验证行为
messageWriter.Message = this.message;
messageWriter.WriteMessage(filePath);
}
else
Console.WriteLine("");
}
private bool ValidateUser() {
//验证用户代码
return true;
}
}
class EncryptedMessageWriter : IMessageWriter {
private string message;
//被装饰者
private IMessageWriter msgWriter;
public EncryptedMessageWriter(IMessageWriter msgWriter) {
this.msgWriter = msgWriter;
}
public string Message {
set { message = value; }
}
public void WriteMessage(string filePath) {
this.msgWriter.Message = "encrytedMsgInBase64";//加密信息
//被装饰得行为
this.msgWriter.WriteMessage(filePath);
}
private string GetPassword() {
Console.WriteLine("Please provide security password");
return Console.ReadLine();
}
}
这里有问题吗?????
我刚刚说这个模式有四个参与者,我已向你展示了Component(IMessageReader,IMessageWriter),ConcreteComponent(MessageReader,MessageWriter)和ConcreteDecorator(SecureMessageWriter,EncryptedMessageWriter)。但Decorator那里去了?在我们的例子中,我们仅仅添加了已存在的行为,没有引进新的行为。我们没有改变其他结构。在这种情况下,我们忽略了实现Decorator,并沿用主要层次结构。这里我不在展示读有关的类,它们仅仅是反向处理。
我们学到了什么
现在如果我需要一个经过用户验证的简单信息写操作,我会这么做:
我用SecureMessageWriter装饰了MessageWriter,它现在在写信息到磁盘前会先验证用户。如果同时需要验证用户和加密信息,我会这么做:
1. 装饰器使我们在大多数情况下避免构造复杂基类,撰写大量的代码。
2. 装饰器允许我们以不同的顺序进行不同的组合,这在别的方法来说不太容易。
3. 相比为不同的行为与合并实现不同的基类,我们可以按需实现单独的需求行为。
回到现实
这一部分我们会看到装饰器模式在实际中应用的例子。
同步包
善于使用.ET中旧集合类,如Queue,ArrayList等的人可能还记得许多类提供的(集合)同步函数。它把集合实例自己作为参数传入,并返回一个同步集合。尽管集合类自己并不同步,但这个方法返回的实际上是从集合类自己继承过来的一个装饰器。例如,在下面代码中当我们调用Syncrhonized(a1)时,我们会收到一个从ArrayList继承来的SyncArrayList的实例。
al = ArrayList.Synchronized(al);
SyncArrayList存储通过属性_list传递的ArrayList,并且按照同样的方法重载不同的实例方法。
lock (this._root){
return this._list.Add(value);
}
}
注意事项
按照此法创建同步包时,要注意叫做“自死锁”现象,这意味着一个占有锁的线程进入另一格方法(或者递归),然后又试图获取同一个对象的锁。在Windows中,如果你使用.NET实施监控,或者内核级命名,或者无名互斥,全部都有重入机制(即递归)。所以你不会遇到此类问题,但是在其他环境(如Linux)中编程时,默认互斥类型是快速互斥(一种非递归互斥),你的代码就可能成为“自死锁”的受害者。假如使用消息,即使在Windows上,它一样没有自我意识,如果你不注意,就会给你带来这个问题。当然,对于一个简单信号,比如n=1,在第二次访问时,你一样会遇到“自死锁”。
同理,你可以为你的集合类实现一个只读包。不像我们到现在所看到的,与其说他们是向类上添加功能,倒不如说去掉一些。例如在重载方法Add()中可能抛出操作不支持的异常。.NET提供了ReadOnlyCollection,它用于包装泛型列表。Java则提供了只读包,如UnmodifiableCollection,UnmodifiableSet等等。
Java中,你可以按照下面方式为很多集合类型获取同步包。
Java和.NET中的IO
Java的java.io包和.NET的Stream类都使用了该模式。我不会就它们的实现谈论很多细节。.NET中,Stream是一个抽象类,提供了基本的行为(如Component),从抽象类Stream继承来的类FileStream,MemoryStream是ConcreteCompents,类BufferedStream,CrytoStream等是ConcreteDecorators。你能清楚地认识到它们同样忽略了Decorator。
同样,在Java中,BufferedReader和FilterReader是Reader的装饰者。而BufferedReader进一步被LineNumberReader装饰。FilterReader被PushbackReader装饰。
收获了什么
装饰器模式允许我们在实现中提供扩展点。你可能注意到,在实现装饰者时,我从来没有涉及到Component类。因此,即使它并不拥有类,一样能通过动态添加行为类装饰它,甚至是递归的方式。这些额外的行为可能在几处行为之前或之后添加,或者这种行为可能被阻止。装饰者能在类中提供新方法,甚至新属性。但装饰器同样有一些问题。
使用装饰器的程序员必须明白它们的意图,否则他最终可能会使用一些毫无意义的组合或序列。比如在我们的场景中,如果程序员按照这种方式定义序列:信息先被压缩,然后加密,最后验证,它将毫无意义。在现实场景中,有些组合或次序可能是灾难性的。
测试一个装饰类需要提供一个模拟的被装饰类。因为我们还没有涉及到测试,所以这里就不说了。
该模式在基本行为上添加一些职责。虽然给装饰者添加新的属性或方法完全合法,但这种方法导致基础类处理问题的灵活性,因为你需要用到具体的实例。