问题的提出
在消息日志功能中,接收到的消息可以直接送往屏幕显示,也可以用文件保存。
不考虑消息日志的全部实现过程,具体代码如下:
interface ILogger{
void log(String msg);
}
class ConsoleLogger implements ILogger{
public void log(String msg) {
Sout;
}
}
class FileLogger implements ILogger {
public void log(String msg) {
DataOutputStream dos = null;
try{
dos = new DataOutputStream(new FielOutputStream("d:log.txt", true));
dos.writeBytes(msg + "\r\n");
dos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
加入现在提出了新需求,接收到的信息科转化成大写字母或转化成 XML 文档,然后屏幕显示或日志保存。常规思路是利用派生类实现。
如果按照继承思路,若需求分析继续变化,则类的数目增加非常快。那么,装饰器模式就是较好的思路之一。
装饰器模式
装饰器模式利用包含代替继承,动态的给一个对象添加一个额外的功能。以消息日志功能为例,装饰器模式类图如图:
抽象修饰器基类 Decorator
abstract class Decorator implements ILogger {
protected ILogger logger;
public Decorator(Logger logger) {
this.logger = logger;
}
}
主要体会“implements ILogger” 中的 “ILogger” 与定义的成员变量 “ILogger logger” 中的 “ILogger”语义有什么不同。需求分析变化了,但无论怎么变,它终究还是一个日志类,因此 Decorator 类要从接口 ILogger 派生。而成员变量 logger 标明 Decorator 对象要对已有的 logger 对象进行装饰,也就是说,要对已有的 FileLogger 或 ConsoleLogger 对象进行装饰。但是由于装饰的内容不同,因此该类只能是抽象类。具体的装饰内容由子类完成。
具体实现类
class UpLogger extends Decorator {
public UpLogger(ILogger logger) {
super(logger);
}
public void log(String msg) {
// 对字符串进行大写修饰
msg = msg.toUpperCase();
// 再执行已有的日志功能
logger.log(msg);
}
}
代码解释:
log() 方法先对字符串大写装饰,再执行已有的日志功能。若已有日志能有有 n 个,则装饰后的字符串可能有 n 个去处,也就是说,该类可以表示 n 个动态含义。
若按照 继承模式变成,则需要编制 n 个 具体的类,从中可知 装饰器模式是采用动态编程的,缩小了程序的规模。(这就是上面提到的 两个 ILogger 的第二个含义的体现)
另一个实现类也是同样的形式。
一个简单的测试类
public class Test{
public static void main(String[] args) throws Exception {
// 已有的日志功能
ILogger existobj = new FileLogger();
// 新的日志装饰类,对 existobj 装饰
ILogger newobj = new XMLLogger(existObj);
......
}
}
装饰器模式主要有 4 中角色
- 抽象构件角色(Component):
他是一个接口,封装了将要实现的方法,如 ILogger - 具体构件角色(ConcreteComponent):
他是多个类,该类实现了 Component 接口,如 FileLogger、ConsoleLogger - 装饰角色(Decorator):
他是一个抽象类,该类实现了 Component 接口,同时也必须持有 Component 的对象的医用,如 Decorator - 具体的装饰角色(Decorator 类的子类):
这些类继承了 类 Decorator ,实现了 Component 接口,描述了具体的装饰过程。如 UpLogger、XMLLogger
深入理解装饰器模式
一本菜谱已在全国发型,特点是具有通用性,但没有考虑地域差异。加入以做白菜和大头菜为例,时间情况是以菜谱为蓝本,在考虑地域差异,比如A 地先吃辣的,B 地喜欢吃甜的,用计算机如何描述
- 定义抽象构件角色 ICook
interface ICook {
// 做菜
void cook()
}
- 定义做白菜,大头菜具体角色
class Vegetable implements ICook {
public void cook() {}
}
class Cabbage implements ICook {
public void cook() {}
}
- 定义抽象装饰器 Decorator
abstract class Decorator implements ICook{
ICook obj;
public Decorator(ICook obj) {
this.obj = obj;
}
}
- 定义具体装饰器
class PepperDecorator extends Decorator {
public PepperDecorator(ICook obj) {
super(obj);
}
private void addPepper() {
// 添加辣椒
}
public void cook() {
addPepper();
obj.cook();
}
}
class SUgarDecorator extends Decorator{
......
}
和上面操作时一样的。
Vegetable 和 Cabbage 是具体角色,也就是说,菜谱中每道菜的做法相当于具体角色,他们都是长期经验的总结。没有这些具体角色,也就他不上与地域相关的特色装饰菜了。好的菜谱有一个重要的特点,一般来说就是只要人们能想到的菜,就能在菜谱中找到。转化为计算机专业术语即是:具体角色相当于底层的具体实现,有哪些实现功能非常重要。如果做得好的话,很大程度上,上层功能相当于对底层功能进行进一步的封装和完善,类似于装饰的功能。
jdk 装饰器模式
抽象构件相当于 Reader,具体构件相当于 InputStream、CharArrayReder 以及 FileReader。该类图中并没有体现抽象装饰器, BufferedReader、LineNumberReader 都是具体装饰器,因为他们都有Reader 类型的成员变量 in。
也可以查看 菜鸟教程的装饰器模式