代理模式和装饰者模式是两种非常常用的设计模式,两者都可以用来扩展对象的行为,因此把这两种设计模式放到一起讨论。
从下图可以发现这种设计模式的类图非常的相似(装饰者模式省略了一些实现)
下面我们分别讨论一下这两种模式,然后再比较他们之间的异同点。
代理模式
首先我们谈代理模式,正如它的名称所示,它为调用方提供一个接口的代理,同时向调用方屏蔽代理的细节,调用方是不清楚这个代理后面究竟是什么,这么做的目的也是显而易见的,代理可以控制被代理接口的行为而不被调用方感知,很多Java框架代码中都能看见代理模式的身影。
现实中也有很多类似代理模式的例子,比如我们去找中介租房的时候,房屋中介就可以看做是房东的代理,这里房东和房屋中介都可以看做是出租者,而我们在租房是往往是不清楚房东的具体情况的(这里点是很重要的)。下面我们可以用代码模拟这种情况。
出租者接口
1
2
3
4// 出租者接口
public interface Lessor<T> {
T lease();
}房东类实现Lessor接口
1
2
3
4
5
6
7
8
9
104j
public class Landlord implements Lessor<House> {
public House lease() {
double sent = 5000.00d;
log.info("出租一套两居室,一个月收{}块吧,嫌麻烦,直接找个中介吧", sent);
return new House().setRent(sent);
}
}房屋中介类实现Lessor接口,并且关联一个房东,注意这里并不一定是通过这种组合的方式关联被代理类。
1
2
3
4
5
6
7
8
9
10
11
124j
public class LettingAgency implements Lessor<House> {
private Lessor<House> landlord = new Landlord();
public House lease() {
log.info("我是XXX中介,把房子交给我吧,很快就能租出去。");
House house = landlord.lease();
house.setRent(house.getRent() * 1.5d);
log.info("加点辛苦费吧");
return house;
}
}承租人找中介租房
1
2
3
4
5
6
7
8
9
104j
public class Lessee {
public static void main(String[] args) {
Lessor<House> lessor = new LettingAgency();
House house = lessor.lease();
log.info("租到了个两居室,花了{}块钱", house.getRent());
}
}
从上面的代码,我们可以发现代理模式的几个关键点
- 代理类(中介)和被代理类(房东)都实现了相同的接口(出租者)
- 代理类可以在被代理类的基础上做一些额外的控制(加中介费)
- 调用方不清楚被代理类的细节(这里承租人只知道租了中介的7500块的房子)
装饰者模式
接下来我们再谈谈装饰者模式,装饰者模式提供一种比继承更加灵活的扩展对象的方法,完全遵循开闭原则
从类图上看,装饰者模式和代理模式非常相似。但是装饰者模式和代理模式不同的地方在于,装饰者模式更加着重于增加被装饰者的行为。下面我们用一个例子来展示装饰者模式可以做什么,这个例子展示如何调制一杯心仪的饮料
首先,我们有个饮料接口
1
2
3
4public interface Beverage {
// 调制饮料
void concoct();
}浓缩咖啡实现了饮料接口
1
2
3
4
5
64j
public class Espresso implements Beverage {
public void concoct() {
log.info("添加浓缩咖啡");
}
}创建一个抽象装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public abstract class BeverageDecorator implements Beverage {
private Beverage beverage;
public BeverageDecorator(Beverage beverage) {
this.beverage = beverage;
}
protected abstract void prepare();
protected abstract void process();
public void concoct() {
prepare();
// 在调用beverage之前可以选择做一下准备工作
beverage.concoct();
// 在完成beverage之后也可以选择做一些处理
process();
}
}创建一个拿铁咖啡,它实现了BeverageDecorator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
164j
public class Latte extends BeverageDecorator {
public Latte(Beverage beverage) {
super(beverage);
}
protected void prepare() {
log.info("先打一杯热牛奶");
}
protected void process() {
log.info("添加大量牛奶");
}
}可以在创建一个焦糖拿铁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
154j
public class CaramelLatte extends BeverageDecorator {
public CaramelLatte(Beverage beverage) {
super(beverage);
}
protected void prepare() {
}
protected void process() {
log.info("添加一些焦糖");
}
}看一看调用方如何使用
1
2
3
4
5
6
7
8
9
10
11public class Barista {
public static void main(String[] args) {
// 我需要一杯原味拿铁
Espresso espresso = new Espresso();
Latte latte = new Latte(espresso);
// 不太合口味,加点焦糖吧
CaramelLatte caramelLatte = new CaramelLatte(latte);
// 开始调制
caramelLatte.concoct();
}
}
从上面的代码,我们总结装饰者模式的几个关键点:
- 装饰者和被装饰者实现同一接口,这点和代理模式一样
- 装饰者可以为被装饰者添加额外的行为(添加原料),这点和代理模式类似,但是目的不同
- 被装饰者是调用方给装饰者的,装饰者只负责添加额外的行为,这点和代理模式完全不同
- 被装饰者可以被多个装饰者装饰,从而被赋予多个额外的行为
总结
通过上述的代码实例,我们很容易发现,代理模式和装饰者模式之间的差异,虽然具有相似的代码结构,也都可以在目标对象的方法调用前后增加行为,但是实现的目的是有区别的:装饰者模式提供一种灵活的扩展对象的方式,被装饰对象一般是由调用方提供的,而代理模式主要是为了控制对对象的访问,被代理对象相对于调用方来说是透明的。
我们会经常在一些Java框架代码中发现代理模式的另一种实现方式————动态代理,关于动态代理的一些实现方式,我们可以在另外的文章中讨论。