设计模式--装饰者模式

装饰者模式顾名思义就是装饰,既然是装饰就需要由装饰者和被装饰者构成,举个例子我们在工位上面放了一盆绿植,那么就可以描述为用绿植装饰了工位(工位是被装饰者,绿植是装饰者)。所以实现一个装饰者模式需要有装饰者和被装饰者元素构成;其实说到装饰者模式,还有一个特性就是在装饰过程中不能本质的改变(代码变化)被装饰者的行为状态,仅仅通过装饰者的装饰就可以轻松的改变被装饰者的行为,而不需要变更被装饰者。

下面用一个例子来具体实现一下装饰者模式,就拿我们生活中购买汽车来举例,比如我们买车时都分很多型号,就拿宝马来讲,分为宝马3系、宝马5系、宝马X5、宝马M6等等;然而每一个系列的车型又分为很多种配置,比如低配(乞丐版)、中配(舒适版)、高配(豪华版)、自定义(自选配置)。下面就以宝马X5、宝马M6系列为例进行实现。

1、被装饰者接口–宝马(BMW)通用接口

因为每一款车都有其对应的型号和价格,所以我们就仅仅定义价格和型号方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 宝马汽车产品抽象类,被装饰者
* @author mapingsheng
*/
public interface BMW {
/**
* 购买的汽车型号
* @return
*/
String getType();
/**
* 购买花费金额,根据你的配置不同花费金额也不同
* @return
*/
double cost();
}

2、被装饰者具体类–新建宝马x5(BMWx5)汽车类,并实现通用的BMW接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 宝马M6系具体产品类
* @author mapingsheng
*/
public class BMWx5 implements BMW {
/**
* 覆写接口的花费金额方法
*/
@Override
public double cost() {
return 98.6;
}
/**
* 覆写接口返回类型方法
*/
@Override
public String getType() {
return "宝马X5系列 "+this.cost()+"万";
}
}

3、被装饰者具体类–新建宝马m6(BMWm6)汽车类,并实现通用的BMW接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 宝马M6系具体产品类
* @author mapingsheng
*/
public class BMWm6 implements BMW {
/**
* 重写接口的花费金额方法
*/
@Override
public double cost() {
return 125.3;
}
/**
* 重写接口返回类型方法
*/
@Override
public String getType() {
return "宝马m6系列 "+this.cost()+"万";
}
}

4、装饰者接口-配件接口

一般你选择什么配置都应该给你提供具体的配件信息吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 配置接口,主要是装饰具体的BMW车,所以可以不用定义任何方法,
* 只需要在原有的BMW具体产品类方法基础上增强方法功能;
* 当然也可以定义自己的方法去增加
* @author mapingsheng
*/
public interface Configure extends BMW {
/**
* 配件信息
* @return
*/
String getConfigInfo();
}

5、装饰者具体类-电动天窗配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 电动天窗配置
* @author mapingsheng
*/
public class ConfigSkylight implements Configure {
BMW bmw;
final double cost = 0.9;
/**
* 在构造方法中注入产品BMW接口,
* 然后再重写对应的方法以此对接口的具体动态产品类进行增强
* @param bmw
*/
ConfigSkylight(BMW bmw){
this.bmw = bmw;
}
/**
* 先获取该接口对应的具体产品类的花费金额,
* 然后加上本配置需要花费的金额
*/
@Override
public double cost() {
return cost+this.bmw.cost();
}
/**
* 先获取该接口对应的具体产品类的类型信息,
* 然后加上本配置描述
*/
@Override
public String getType() {
return this.bmw.getType()+"\n 【电动天窗 "+this.getConfigInfo()+"】 "+cost+"万";
}
/**
* 电动天窗的基本配置信息
*/
@Override
public String getConfigInfo() {
return "电压:12v、材质:玻璃、打开方式:电动、是否全景:否 ";
}
}

6、装饰者具体类-导航系统配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 导航系统配置
* @author mapingsheng
*/
public class ConfigNavigation implements Configure {
BMW bmw;
final double cost = 0.5;
/**
* 在构造方法中注入产品BMW接口,
* 然后再重写对应的方法以此对接口的具体动态产品类进行增强
* @param bmw
*/
ConfigNavigation(BMW bmw){
this.bmw = bmw;
}
/**
* 先获取该接口对应的具体产品类的类型信息,
* 然后加上本配置描述
*/
@Override
public String getType() {
return this.bmw.getType()+"\n 【导航系统 "+this.getConfigInfo()+"】 "+cost+"万";
}
/**
* 先获取该接口对应的具体产品类的花费金额,
* 然后加上本配置需要花费的金额
*/
@Override
public double cost() {
return cost+this.bmw.cost();
}
/**
* 导航设备配置信息
*/
@Override
public String getConfigInfo() {
return "尺寸:12X36、是否触屏:是、分辨率:1028、内存:64G";
}
}

7、装饰者具体类-自动驾驶配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 自动驾驶配置
* @author mapingsheng
*/
public class ConfigAutopilot implements Configure {
BMW bmw;
final double cost = 3.8;
/**
* 在构造方法中注入产品BMW接口,
* 然后再重写对应的方法以此对接口的具体动态产品类进行增强
* @param bmw
*/
ConfigAutopilot(BMW bmw){
this.bmw = bmw;
}
/**
* 先获取该接口对应的具体产品类的类型信息,
* 然后加上本配置描述
*/
@Override
public String getType() {
return this.bmw.getType()+"\n 【自动驾驶系统 "+this.getConfigInfo()+"】 "+cost+"万";
}
/**
* 先获取该接口对应的具体产品类的花费金额,
* 然后加上本配置需要花费的金额
*/
@Override
public double cost() {
return cost+this.bmw.cost();
}
@Override
public String getConfigInfo() {
return "切换方式:手动、语音控制:否、路线规划:自动";
}
}

8、测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
BMW bmwX5 = new BMWx5();//宝马x5基本配置
bmwX5 = new ConfigSkylight(bmwX5); //为宝马x5装饰天窗配件
bmwX5 = new ConfigNavigation(bmwX5); //为宝马x5装饰导航配件
BMW bmwM6 = new BMWm6();//买一个基本款的宝马m6,不需要额外装饰
System.out.println(bmwX5.getType()+"\n "+bmwX5.cost()+"万");
System.out.println(bmwM6.getType()+"\n "+bmwM6.cost()+"万");
}
}

以上代码运行结果:

宝马X5系列 98.6万

【电动天窗 电压:12v、材质:玻璃、打开方式:电动、是否全景:否 】 0.9万

【导航系统 尺寸:12X36、是否触屏:是、分辨率:1028、内存:64G】 0.5万

100.0万

宝马m6系列 125.3万

125.3万

9、小结

通过上面的买车场景实现了一个简单的装饰者模式,我们可以看到上面的代码中的“被装饰者具体类”定义之后,自始至终其代码就没有改变过,仅仅通过“装饰者具体类”去装饰就达到了使被装饰者的行为方法发生变更。

所以装饰者模式的设计原则为:对扩展开放、对修改关闭,这句话体现在我如果想扩展被装饰者类的行为,无须修改被装饰者抽象类(接口),只需继承(实现)装饰者抽象类(接口),实现额外的一些装饰即可对被装饰者进行包装,比如我们想对BMWx5基本款进行配件添加,BMWx5具体类不需要更改,只需要新增一个装饰者(配件)具体类,然后对BMWx5进行装饰即可。

装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)

装饰者可以在被装饰者的行为前面与/或后面加上自己的行为已达到增强被装饰者行为的目的。

装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂,因为你一个“被装饰者”可能会被多个“装饰者”所装饰,并且每个装饰者之间可能无任何关系,所以你需要定义很多个装饰者类。

还有一点需要明确声明,并不是在任何情况下,被装饰者一旦定义,就不能更改,只能通过装饰者去更改;而是在一些情况下时需要更改被装饰者的,比如上面的被装饰者BMW,现在我们可以为其新增一个轮胎方法,因为每一辆车可以没有天窗、可以没有导航,但是必须都有轮胎,所以说针对这些共性的东西是需要通过更改被装饰者去处理的。