Java设计方式之装饰模式
说明:
装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。装饰模式的特点;
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
(2)
装饰对象包含一个真实对象的索引(reference)
(3) 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。
(4)
装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
下表格列举了装饰模式和继承的不同:
装饰模式 VS 继承
装饰模式 继承用来扩展特定对象的功能 用来扩展一类对象的功能
不需要子类 需要子类
动态地 静态地
运行时分配职责 编译时分派职责
防止由于子类而导致的复杂和混乱 导致很多子类产生,在一些场合,报漏类的层次
更多的灵活性 缺乏灵活性
对于一个给定的对象,同时可能有不同的装饰对象,客户端可以通过它的需要选择合适的装饰对象发送消息。 对于所有可能的联合,客户期望
很容易增加任何的 困难
例子:
让我们重新返回我们在工厂方法和单例模式log实用工具上,我们的模式主要由Logger
接口和两个它的实现类??FileLogger和ConsoleLogger??分别把信息出力到一个文件和屏幕中。另外,还有包括工厂方法的LoggerFactory类。
LoggerFactory没有出现在下图中,主要是因为它和现在讨论的例子没有直接联系。
让我们想象一些客户端需要以超出Logger
Utility现在所提供的新的方式出力信息,客户端需要下面两种特征;
(1) 把出力的信息传唤为HTML文档
(2)
对出力信息进行逻辑转化的简单加密,在面向对象的设计中,不改变现存的类的代码,可以应用继承来增加新的功能。例如,子类化现在的类重载它的方法来增加所需要的新功能。
应用继承,我们要子类化FileLogger和ConsoleLogger类来增加新的功能,会有下面的一组新的子类:
子类 父类
功能
HTMLFileLogger FileLogger 转化出力信息为HTML文档,并存入一个Log文件
HTMLConsLogger
ConsoleLogger 转化出力信息为HTML文档,并显示在屏幕上
EncFileLogger FileLogger
加密出力信息,并存入一个Log文件
EncConsLogger ConsoleLogger 加密出力信息,并显示在屏幕上
从类图可以看到,为了实现新的功能加入了一组新的子类。如果我们还有其他的Logger类型(例如:DBLogger出力信息到数据库中),这样会有更多子类。当一个新的特性需要被加入,子类的数量会有成倍数的增长,同时我们会有一个庞大的类层次。
装饰模式使我们从这种情景中解脱出来,装饰模式推荐通过对象的合成而不是继承来包装一个对象扩展它的功能。
应用装饰模式,让我们为Logger
Utility定义一个有下列特征的默认根装饰类LoggerDecorator:
(1) LoggerDecorator包括一个Logger实例的引用。这个引用指向它包含的Logger对象。
(2)
LoggerDecorator实现Logger借口、提供Log方法的基本的默认实现,他只是简单的转发调用给它包含的Logger
对象。每一个LoggerDecorator子类保证定义log方法。
Listing 19.1: LoggerDecorator Class
- public class LoggerDecorator implements Logger {
- Logger logger;
- public LoggerDecorator(Logger inp_logger) {
- logger = inp_logger;
- }
- public void log(String DataLine) {
- /*
- Default implementation
- to be overriden by subclasses.
- */
- logger.log(DataLine);
- }
- }//end of class
让我们定义LoggerDecorator的两个子类,HTMLLogger和EncryptLogger。 #p# 具体的Logger 装饰类
HTMLLogger
HTMLLogger重载了log方法的默认实现。在log方法中,装饰类把出力信息转化为HTML文档,并且发送给可以出力的Logger实例。Listing 19.2: HTMLLogger Class
- public class HTMLLogger extends LoggerDecorator {
- public HTMLLogger(Logger inp_logger) {
- super(inp_logger);
- }
- public void log(String DataLine) {
- /*
- Added functionality
- */
- DataLine = makeHTML(DataLine);
- /*
- Now forward the encrypted text to the FileLogger
- for storage
- */
- logger.log(DataLine);
- }
- public String makeHTML(String DataLine) {
- /*
- Make it into an HTML document.
- */
- DataLine = "" + "" + DataLine +
- "" + "";
- return DataLine;
- }
- }//end of class
EncryptLogger
与HTMLLogger相似,EncryptLogger重载了log方法,在log方法中,EncryptLogger通过简单的将字符位置向右转移一位实现了加密逻辑,并且发送给可以出力的Logger实例。Listing 19.3: EncryptLogger Class
- public class EncryptLogger extends LoggerDecorator {
- public EncryptLogger(Logger inp_logger) {
- super(inp_logger);
- }
- public void log(String DataLine) {
- /*
- Added functionality
- */
- DataLine = encrypt(DataLine);
- /*
- Now forward the encrypted text to the FileLogger
- for storage
- */
- logger.log(DataLine);
- }
- public String encrypt(String DataLine) {
- /*
- Apply simple encryption by Transposition…
- Shift all characters by one position.
- */
- DataLine = DataLine.substring(DataLine.length() − 1) +
- DataLine.substring(0, DataLine.length() − 1);
- return DataLine;
- }
- }//end of class
为了使用新设计装饰对象,客户端需要:
(1) 使用LoggerFactory工厂方法创建一个合适的Logger实例(FileLogger/ConsoleLogger)。
(2)
把第一步中创建的Logger实例作为参数转递给新创建的合适的LoggerDecorator实例的构造函数。
(3)
调用LoggerDecorator实例上的方法,
Listing 19.4: Client DecoratorClient Class
- class DecoratorClient {
- public static void main(String[] args) {
- LoggerFactory factory = new LoggerFactory();
- Logger logger = factory.getLogger();
- HTMLLogger hLogger = new HTMLLogger(logger);
- //the decorator object provides the same interface.
- hLogger.log("A Message to Log");
- EncryptLogger eLogger = new EncryptLogger(logger);
- eLogger.log("A Message to Log");
- }
- }//End of class