设计模式(四)装饰器模式

在代码运行期间动态增加功能的方式,称之为装饰器(Decorator),装饰器的好处是,为一个类的对象添加新的功能,而无需继承。


装饰器模式例子

一个图形接口

1
2
3
public interface Shape {
void draw();
}

矩形

1
2
3
4
5
6
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("draw a rectangle");
}
}

矩形绘制

1
2
3
4
5
public static void main(String[] args) {
// 普通矩形
Shape rectangle = new Rectangle();
rectangle.draw();
}

现在,我们增强图形 自身 的功能,让它在绘制的时候会发光!我们不想改接口,也不想在每个实现类中都去写发光的代码(因为都一样)。

解决办法:新增一个图形发光装饰类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 为 shape 添加发光功能
*/
public class ShapeLightingDecorator implements Shape {

private Shape shape;

public ShapeLightingDecorator(Shape shape) {
this.shape = shape;
}

public void lighting(){
System.out.println("lighting when draw");
}

// 绘制时发光
@Override
public void draw() {
shape.draw();
lighting();
}
}

看看效果

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
// 普通矩形
Shape rectangle = new Rectangle();
rectangle.draw();

// 装饰过发光功能的矩形
Shape lightingRectAngle = new ShapeLightingDecorator(new Rectangle());
lightingRectAngle.draw();
}

装饰器模式跟代理模式的区别

两者都是对类的方法进行扩展,但 装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了;代理模式则强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

强行区分这两者的区别并没有太多意义,毕竟他们之间的边界确实比较模糊。

参考:


JDK中使用 Decorator 模式的例子

IO流

1
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

BufferedInputStream 装饰了 inputStream 。

java.io 包中,输入流的抽象类是 InputStream, FileInputStream 继承 InputStream 并拥有文件输入流的特性。而 FliterInputStream 是一个抽象装饰器,我们熟悉的 BufferedInputStream 正是 FliterInputStream 的一个具体类,它包装一个了 InputStream,并为之扩展缓冲的功能。

图片来自《Head First 设计模式》

在一些框架中,如 Spring,使用了 Decorator pattern 的一般类名中都含有 Wrapper 或 Decorator。作用基本上都是动态地给一个对象添加一些额外的职责。


Python 中的 Decorator

在 Python 中实现装饰器非常简单

首先我们要绘制:

1
2
def draw():
print("draw")

现在想在绘制之后发光,但又不想修改 draw() 本身,可以用装饰器来加强 draw,这个装饰器传入了一个函数,对函数进行增强,然后返回增强后的函数

1
2
3
4
5
def draw_lighting_decorator(func):
def wrapper():
func()
print("lighting")
return wrapper

调用

1
2
3
4
5
#  draw 是一个函数
draw = draw_lighting_decorator(draw)

# 函数调用
draw()

输出:

1
2
draw
lighting

语法糖

Python 提供了 @ 语法糖,让我们不必显式调装饰函数,只需要在原函数上加上注解,自动装饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def draw_lighting_decorator(func):
@functools.wraps(func)
def wrapper():
func()
print("lighting")
return wrapper


@draw_lighting_decorator
def draw():
print("draw")


if __name__ == '__main__':
draw()

@functools.wraps(func) 的作用是,在装饰后,函数名还是 draw() 函数的名,而不是 draw_lighting_decorator() 函数的名