【IT168 评论】过去我的一个朋友常说,学习任何编程语言最困难的部分是运行“Hello World”,之后一切都很容易。多年以后,我才意识到他说的很对。学习设计模式的基本目标是要用它,尤其是帮助那些有扎实的OOP基础,而对设计模式很困惑的人在设计中应用它。我不会为不同设计模式写很全面的参考,但我希望这些文章能让你入门。设计模式与特定的语言无关。虽然我用C#写了很多示例,但我尽量避免一些C#特有的结构,因此它面向大部分人,尤其是使用C++的人。
装饰器模式允许我们动态为对象添加行为。下面我们先介绍一个场景,然后寻找替代方法。这会帮助我们认清该模式的真实用途,尤其在灵活性这方面。
思考过程
今天我们参考的不是一个实际的场景,有时可能会很奇怪,但它会帮助我们弄清这个模式及相关概念。我们的目标是添加将不同来源的信息存储到磁盘文件上的功能。所以,第一步我们定义一个接口,并实现它。IMessageWriter和IMessageReader接口分别用于写入和读取信息,他们的实现如下:
string Message { set; }
void WriteMessage(string filePath);
}
class MessageWriter : IMessageWriter {
private string message;
public string Message { set{message =value;} }
public virtual void WriteMessage(string filePath) {
File.WriteAllText(filePath, message);
}
}
interface IMessageReader {
string ReadMessage(string filePath);
}
class MessageReader : IMessageReader {
public virtual string ReadMessage(string filePath) {
if (File.Exists(filePath))
return File.ReadAllText(filePath);
return null;
}
}
信息作为Message属性存储,MessageWriter的方法WriteMessage把它写到指定的文件。同样,MessageReader的方法ReadMessage从指定的文件读取,并以字符串的形式返回。现在假设客户提出了新需求。
对某些信息在读和写文件之前,我们需要验证用户;
对某些信息我们希望加密后保存,来防止别人读取,并且我们需要以64位编码保存加密信息;
对某些信息,我们都需要这些功能;
很奇怪吧,呵呵,首先我们不用装饰器分析不同的解决方案,这会使我们对这个简单的设计模式认识更加清晰。
传统解决方案
你决定在原来行为上使用继承。你从MessageWriter继承EncryptedMessageWriter来实现加密行为。
public override void WriteMessage(string filePath) {
//加密消息
//转换到64位编码
Message = "base64StringYouGotFromAboveCode";//存储信息
base.WriteMessage(filePath);
}
}
同样,你从EncrytedMessageWriter继承SecureMessageWriter来实现用户验证。
public override void WriteMessage(string filePath) {
if (ValidateUser())
base.WriteMessage(filePath);
else
Console.WriteLine("No message saved,user validation failed.");
}
private bool ValidateUser() {
//验证用户,失败返回false
return true;
}
}
现在我们能写入加密的信息,或经过用户验证后的加密信息。那么如果需要写入一些只需要用户验证而不需要加密的简单文本信息时,我们该怎么办?你可以在EncryptedMessageWriter中写入一些丑陋的判断,在不需要加密的时候跳过加密。假设遇到此类情况你还这么做,那么那些操作换个顺序呢,例如我们想先加密后验证,如果验证失败,则除64位编码加密消息外在做点别的。很显然,上面的组织结构无法处理这种情况。谁能阻止用户提出更多的需求,像消息需要数据签名,大消息需要压缩或不需要加密,对于某些信息,写到磁盘后,你必须在消息队列中输入文件路径和时间戳以便其他程序读取,甚至写到数据库中,等等等等?!显然不能。
让我们只关注验证,忽略其他细节,评估一下你面对情况的复杂性和严重性。目前,我们在加密消息时实现了用户验证。现在我们需要满足其他相同的功能,如:CompressedMessageWriter,DigitallySignedMessageWriter等。你唯一能做的是实现SecureCompressedMessageWriter,SecureDigitallySignedMessageWriter等。同样对其他大量的组合,像压缩加密信息,简单信息压缩等等。天哪,你真的坠入“子类地狱”了。
第二个解决方案是写一个非常大的MessageReader,处理所有提到的需求功能。随着时间流逝,它变得越来越复杂,越来越难以维护——非常不推荐这样。
第三个解决方案可能是上面两种方案的合并,这可能是治标不治本。
引入装饰器模式
这恰恰是装饰器模式解决的问题。如果你仔细观察上面采用继承的解决方案,你会认识到问题的根源是继承带来的静态关联。这些关联被嵌入到类之间,并且不能在运行时改变。装饰器用包含替换关联,而包含是一种非常灵活且在运行时能被更新的对象关联。
首先让我们看看装饰器模式究竟是什么。下面是装饰器模式的类图。
四个参与者:
Component:定义一个对象接口,可以动态的给对象添加职责. 在我们的例子中是IMessageWriter和IMessageReader;
ConcreteComponent: 定一个实现Component接口的对象。这个对象会被装饰,但它不会包含任何装饰者的信息,而装饰者不会访问它的实现。在我们例子中是MessageWriter和MessageReader。
Decorator: 包含一个Component对象的引用,定义一个与Component一致的接口。所以它包含一个指向基本行为的引用,并且实现了相同的接口,因此能被Component自己访问。客户端代码期望Component能不需要关心装饰者之间的差别处理它们。
ConcreteDecorator: 它向Component添加职责。从Decorator继承来的类可以统一添加新方法的形式添加一些额外的功能。
到目前为止,我们已有两部分,分别是Component:基本行为,即在我们例子中是IMessageWriter和用于读的IMessageReader;和ConcreteComponent,即我们实现的读写行为:MessageWriter和MessageReader。