设计模式之装饰模式和外观模式

装饰模式

描述:向某个对象动态地添加更多的功能。修饰模式是除类继承外另一种扩展功能的方法。
装饰模式

什么时候使用装饰模式?
装饰模式是为已有功能动态添加更多功能的一种方式,当系统需要新功能时,是向旧类添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,在主类中加入了新的字段、方法、逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在特殊需求下才会执行的行为。
装饰模式提供了很好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户端代码就可以根据需要有选择地,按顺序地包装对象。
装饰模式有4个重要角色

  • Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

  • ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。

  • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

  • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

如果只有一个ConcreteComponent,没有Component,那Decorator可以是ConcreteComponent的一个子类,同理,如果只有一个ConcreteDecorator,那就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator在责任合并成一个类

举个茶饮品的例子,茶饮品可以分为奶茶和果茶两大主类,我们可以给饮品加料,既可以不加,也可以来份全家福,还可以只选其中几种加料。像这种多变的需求,就可以用装饰模式来装饰茶饮品

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//Component
abstract class Tea {
abstract String getType();

abstract int cost();
}

//ConcreteComponent
class MilkTea extends Tea {
@Override
String getType() {
return "奶茶";
}

@Override
int cost() {
return 5;
}
}

class FruitTea extends Tea {
@Override
String getType() {
return "果茶";
}

@Override
int cost() {
return 8;
}
}

//Decorator
abstract class AddStuff extends Tea {
protected Tea tea;

public AddStuff(Tea tea) {
this.tea = tea;
}

@Override
String getType() {
return tea.getType();
}

@Override
int cost() {
return tea.cost();
}
}

//ConcreteDecorator
class Pearl extends AddStuff {
public Pearl(Tea tea) {
super(tea);
}

@Override
String getType() {
return "珍珠" + super.getType();
}

@Override
int cost() {
return super.cost() + 1;
}
}

class Pudding extends AddStuff {
public Pudding(Tea tea) {
super(tea);
}

@Override
String getType() {
return "布丁" + super.getType();
}

@Override
int cost() {
return super.cost() + 2;
}
}

主程序

1
2
3
4
5
6
7
8
9
public class Decorator {
public static void main(String[] args) {
Tea tea1 = new Pudding(new Pearl(new MilkTea()));
Tea tea2 = new Pearl(new Pudding(new FruitTea()));
System.out.println("点了一杯 " + tea1.getType() + ",售价" + tea1.cost() + "元。");
System.out.println("点了一杯 " + tea2.getType() + ",售价" + tea2.cost() + "元。");
}

}

总结:装饰模式的优点是把类中的装饰功能从类中搬移出去,简化原有类,有效地把类中的核心职责和装饰功能区分开了。

外观模式

描述:为子系统中的一组接口提供一个一致的界面, 外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式

外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。

外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。

外观模式包含如下2个角色:

  • Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。

  • SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。

外观模式的目的不是给予子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。

外观模式的本质是:封装交互,简化调用

什么时候用外观模式?

  • 在设计初期阶段,应该有意识的将不同的两个层分离,比如dao和service之间分层,service和controller之间分层。

  • 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加Facade可以提供一个简单的接口,减少它们之间的依赖。

  • 维护遗留的大型系统时,可能这个系统以及很难维护和扩展了,增加Facade,来提供设计粗糙或高复杂的遗留代码比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。

外观模式十分简单,又常用,是很好的设计模式。因此直接上代码,不多BB

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class Facade {
public static void main(String[] args) {
Fund fund = new Fund();
fund.buyFund();
fund.sellFund();
}
}

//Facade
class Fund {
private Stock stock;
private NationalDebt nationalDebt;
private Realty realty;

public Fund() {
stock = new Stock();
nationalDebt = new NationalDebt();
realty = new Realty();
}

public void buyFund() {
stock.buy();
nationalDebt.buy();
realty.buy();
}

public void sellFund() {
stock.sell();
nationalDebt.sell();
realty.sell();
}
}

//SubSystem
class Stock {
public void sell() {
System.out.println("卖股票");
}

public void buy() {
System.out.println("买股票");
}
}

class NationalDebt {
public void sell() {
System.out.println("卖国债");
}

public void buy() {
System.out.println("买国债");
}
}

class Realty {
public void sell() {
System.out.println("卖房产");
}

public void buy() {
System.out.println("买房产");
}
}

参考资料:

0%