设计模式之行为型模式

这一章我们来学习行为型模式。包括以下12种:

  • 责任链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 解释器模式(Interpreter Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 备忘录模式(Memento Pattern)
  • 观察者模式(Observer Pattern)
  • 状态模式(State Pattern)
  • 空对象模式(Null Object Pattern)
  • 策略模式(Strategy Pattern)
  • 模板模式(Template Pattern)
  • 访问者模式(Visitor Pattern)

这些设计模式特别关注对象之间的通信。


责任链模式

责任链模式为请求创建了一个接受者对象链。这种模式给予请求的类型,对请求的发送者和接受者解耦。通常每个接受者都包含对另一个接收者的引用,如果一个对象不能处理该请求,那么请求依次传递。
责任链模式避免发送者与接收者耦合在一起,让多个对象都有可能接收请求,让这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它。如果我们需要在处理消息时经过多道过滤,那么可以选择责任链模式。
可以把责任链模式理解为“贪官污吏克扣钱粮”,每一季按照货物的多少进行扒皮,进行修改,只要符合自己的逻辑。
责任链模式涉及到的角色如下所示:

  • 抽象处理者(Handler):定义出一个处理请求的接口。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。
  • 具体处理者(ConcreteHandler):具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。

下面这个例子中,将信息依次传递,依次处理信息。

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
abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
//持有下家的引用
protected AbstractLogger nextLogger;
public void setNextLogger (AbstractLogger nextLogger) {
this.nextLogger = nextLogger;
}
//业务逻辑,需要的时候向下传递
public void logMessage (int level, String message) {
if (this.level <= level)
write(message);
if (nextLogger != null)
nextLogger.logMessage(level, message);
}
abstract protected void write(String message);
}
//处理消息
class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("console logger" + message);
}
}
class ErrLogger extends AbstractLogger {
public ErrLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("err logger" + message);
}
}
class FileLogger extends AbstractLogger {
public FileLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("file logger" + message);
}
}

public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers() {
AbstractLogger errorLogger = new ErrLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new ErrLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ErrLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO, "Information");
loggerChain.logMessage(AbstractLogger.DEBUG, "Debug");
loggerChain.logMessage(AbstractLogger.ERROR, "Error");
}
}


命令模式

命令模式是一种数据驱动的设计模式,属于行为模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适对象,并把该命令传递给对象,该对象执行命令。
当我们按下电脑主机键时,电脑开始启动。过了几十秒,电脑开机了,可以使用了。那么在这几十秒内经历了什么呢?其实,这个过程比较复杂,按下按钮后,电源向各个部分供电,主板的BIOS加电后自检、检测其他硬件,然后BIOS才按照用户的配置加载OS,等OS初始化完毕,电脑就进入了我们常见的工作模式。那么这一过程用软件如何描述呢?这一过程对于客户来说,只是按一个按钮而已,而机箱内部具体实现和用户无关,内部流程按照一个逻辑模式一步一步推进。这个问题就是典型的命令模式。
命令模式的角色包括:

  • Command:定义命令的接口,声明执行的方法。
  • ConcreteCommand:命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口,相当于开关。
  • Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

这个例子中,首先创建作为 命令 的接口Order,然后创建作为请求的Stock类。具体实现命令的是BuyStock和SellStock。然后Broker是Invoker,命令的入口。最后,在客户端中调用Broker,来实现命令。

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
//order,command
interface Order {
void execute();
}
//request,concrete command
class Stock {
private String name = "ABC";
private int quantity = 10;
public void buy() {
System.out.printf("Stock[Name:%s, Quantity:%d] bought\n", name, quantity);
}
public void sell() {
System.out.printf("Stock[Name:%s, Quantity:%d] sold\n", name, quantity);
}
}
//receiver
class BuyStock implements Order {
private Stock stock;
public BuyStock(Stock stock) {
this.stock = stock;
}
@Override
public void execute() {
stock.buy();
}
}
class SellStock implements Order {
private Stock stock;
public SellStock(Stock stock) {
this.stock = stock;
}
@Override
public void execute() {
stock.sell();
}
}
//Invoker
class Broker {
private List<Order> orderList = new ArrayList<>();
public void takeOrder(Order order) {
orderList.add(order);
}
public void placeOrders() {
for (Order order : orderList)
order.execute();
orderList.clear();
}
}
//execute order, client
public class CommandPatternDemo {
public static void main(String[] args) {
Stock stock = new Stock();
BuyStock buyStockOrder = new BuyStock(stock);
SellStock sellStockOrder = new SellStock(stock);
Broker broker = new Broker();
broker.takeOrder(buyStockOrder);
broker.takeOrder(sellStockOrder);
broker.placeOrders();
}
}


解释器模式

解释器模式提供了评估语言的语法或表达式方式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式主要用作SQL解析,符号处理引擎等等。
如果一个特定类型的问题发生的频率很高,那么就值得将该问题的各个实例表述为一个简单的句子,那么就可以构建一个解释器,并通过解释这些句子来解决问题。可以将一个需要解释执行的语言中句子表示为一个抽象语法树,来递归调用解决问题。
需要注意的是,虽然解释器模式可扩展性比较好、灵活,易于实现简单的文法,但可利用场景还是比较少的,对于复杂的文法难以维护。一般在java开发中,无须使用解释器模式,可以使用第三方解析工具包,如expression4j, Jep等。这里有简单的介绍
解释器模式的角色:

  • 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作。具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成。
  • 终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一半是文法中的运算单元,比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
  • 非终结符表达式:文法中的每条规则对应于一个非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
  • 环境角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。

这个例子中,Expression接口是抽象解释器,内部定义了interpret()方法。TerminalExpression是终结符解释器,在这个类中存放这个解释器的逻辑,也就是传入的字符串是否包含原字符串。OrExpression和AndExpression实现了Expression,分别表达了自己的或、与内部逻辑。最终,在客户端中使用了解释器,验证两个参数是否包含原字符串。

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
//抽象解释器
interface Expression {
public boolean interpret(String context);
}
//终结符解释器TerminalExpression
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
if (context.contains(data))
return true;
return false;
}
}
class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
public class InterpreterPatternDemo {
public static Expression getMaleExpression() {
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
public static Expression getMarriedWomanExpression() {
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarried = getMarriedWomanExpression();
System.out.println("John is male?" + isMale.interpret("John"));
System.out.println("Julie is married woman?" + isMarried.interpret("Married Julie"));
}
}


迭代器模式

迭代器模式用于顺序访问集合对象的元素,不需要知道集合对象的底层展示,在java和.Net编程环境中非常常用的设计模式。
java中这种模式最典型的例子就是Iterator。在每次迭代中,可以查看set, list, map的元素,并能进行一些操作。而非常方便的for-each操作内部其实就是实现了Iterator。如果我们想要在自己的程序中使用迭代,那么实现Iterable接口,重写方法,然后就可以在使用中迭代这个类了,非常的方便。
迭代器模式实际上就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明的访问集合内部的数据。
这个例子中,巧妙的实现了Iterator这个结构,主要的就是hasNext()和Next()方法,可以在客户端中进行迭代。

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
//Iterator接口以及容器Container
interface Iterator {
public boolean hasNext();
public Object next();
}
interface Container {
public Iterator getIterator();
}
//创建容器类,具有一个实现Iterator接口的内部类
class NameRespository implements Container {
public String names[] = {"Robert", "John", "Julie", "Lora"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {
int index;
@Override
public boolean hasNext() {
if (index < names.length)
return true;
return false;
}
@Override
public Object next() {
if (this.hasNext())
return names[index++];
return null;
}
}
}
public class IteratorPatternDemo {
public static void main(String[] args) {
NameRespository nameRespository = new NameRespository();
for (Iterator it = nameRespository.getIterator(); it.hasNext(); ) {
String name = (String) it.next();
System.out.println("Name:" + name);
}
}
}


中介者模式

中介者模式是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介对象封装的是对象交互,使对象不需要显式相互引用,从而使耦合松散,并独立改变他们的交互。
当对象之间存在大量的关联关系时,会导致系统的结构变得很复杂,如果一个对象作出改变,那么我们也需要跟踪与之相关联的对象,同时作出相应的处理。这时我们就可以采用中介者模式。典型的例子就是MVC模式,controller就是model和view之间的中介者。
中介者模式降低了类的复杂度,将一对多转化成一对一,并将各个类解耦。缺点是会使得中介者类变得庞大并难以维护。
这个例子中,聊天室chatroom就是用户user的中介,负责将每个用户的消息打印出来。

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
class ChatRoom {
public static void showMessage(User user, String message) {
System.out.println(new Date().toString()+ "," + user.getName() + "," + message);
}
}
class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sendMessage(String message) {
ChatRoom.showMessage(this, message);
}
}
public class MediatorPatternDemo {
public static void main(String[] args) {
User robert = new User("Robert");
User john = new User("John");
robert.sendMessage("hi");
john.sendMessage("no hi!");
}
}


备忘录模式

备忘录模式保存一个对象的某个状态,以便在适当的时候恢复对象。
很多时候我们总是需要记录一个对象的内部状态,这样的目的是为了允许用户取消不确定或者是错误的操作,能够恢复到他原来的状态,也就是传说中的“后悔药”。
备忘录模式保存着对象,给用户提供了一种可恢复状态的机制,使用户能方便的回退到某个历史状态,而且对信息封装,用户也不必关心具体的细节。但每次保存都会消耗一定的内存,随着事态的发展,备忘对象会越来越多,也会渐渐消耗系统的资源。
下面这个例子中,Memento是备忘录,内部存储着状态信息,Originator是业务类,使用状态,并可以从备忘录中存入取出状态信息。CareTaker保存着备忘录的队列,可以存入取出状态。

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
//备忘类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
//业务类
class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//存入备忘录
public Memento sageStateToMemento() {
return new Memento(state);
}
//从备忘录取出
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
//存放备忘录
class CareTaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento state) {
mementoList.add(state);
}
public Memento get(int index) {
return mementoList.get(index);
}
}

public class MenmentoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.sageStateToMemento());
originator.setState("State #3");
careTaker.add(originator.sageStateToMemento());
originator.setState("State #4");

System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved state:" + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved state:" + originator.getState());
}
}


观察者模式

观察者模式是对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式的应用范围很广,GUI中键盘、鼠标监听就属于典型的观察者模式。从这个例子中,我们也可以清楚的看出观察者模式的核心:当被监听对象发生变化时,通知所有监听器接收这个变化。
观察者模式的角色如下:

  1. 抽象目标角色(Subject):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。
  2. 抽象观察者角色(Observer):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。
  3. 具体目标角色(Concrete Subject):将有关状态存入各个Concrete Observer对象。当它的状态发生改变时, 向它的各个观察者发出通知。
  4. 具体观察者角色(Concrete Observer):存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向Concrete Subject对象的引用。
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
//目标角色
class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
//通知每个观察者
public void notifyAllObservers() {
for (Observer observer : observers)
observer.update();
}
}
//抽象观察者角色
abstract class Observer {
protected Subject subject;
public abstract void update();
}
//具体的观察者
class BinaryObserver extends Observer {
public BinaryObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println("Binary String:" + Integer.toBinaryString(subject.getState()));
}
}
class OctalObserver extends Observer {
public OctalObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println("Octal String:" + Integer.toOctalString(subject.getState()));
}
}
class HexaObserver extends Observer {
public HexaObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println("hexa String:" + Integer.toHexString(subject.getState()));
}
}

public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();

new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);

System.out.println("First state change : 15");
subject.setState(15);
System.out.println("Secnd state change: 10");
subject.setState(10);
}
}

___

状态模式

在状态模式中,类的行为是基于它的状态改变的。状态模式允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理。最直接的解决方案是将这些所有可能发生的情况全都考虑到。然后使用if else语句来做状态判断来进行不同情况的处理。但是对复杂状态的判断就显得“力不从心了”。随着增加新的状态或者修改一个状体(if else(或switch case)语句的增多或者修改)可能会引起很大的修改,而程序的可读性,扩展性也会变得很弱。维护也会很麻烦。这种情况我们可以考虑只修改自身状态的模式。再比如,按钮来控制一个电梯的状态,一个电梯开们,关门,停,运行。每一种状态改变,都有可能要根据其他状态来更新处理。例如,开门状体,你不能在运行的时候开门,而是在电梯定下后才能开门。
状态模式包含如下角色:

  1. Context: 环境类。可以包括一些内部状态。
  2. State: 抽象状态类。State定义了一个所有具体状态的共同接口,任何状态都实现这个相同的接口,这样一来,状态之间就可以互相转换了。
  3. ConcreteState: 具体状态类。具体状态类,用于处理来自Context的请求,每一个ConcreteState都提供了它对自己请求的实现,所以,当Context改变状态时行为也会跟着改变。

这个例子中,State接口是状态接口使具体状态类都实现同一个方法。Context类是环境类,包含着内部状态,同时也可以对其进行处理。StartState和StopState是具体状态类,将Context类的状态进行修改。

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
interface State {
public void doAction(Context context);
}

class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("start state");
context.setState(this);
}
public String toString() {
return "Start State";
}
}
class StopState implements State {
@Override
public void doAction(Context context) {
System.out.println("stop state");
context.setState(this);
}
public String toString() {
return "Stop State";
}
}

class Context {
private State state;
public Context() {
state = null;
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
}

public class StatePattern {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}


空对象模式

在空对象模式中,一个空对象取代null对象实例的检查。null对象不是检查空值,而是反应一个不做任何动作的关系。这样,空对象也可以在数据不可用的时候提供默认行为。
在这个模式中,我们创建一个指定各种要执行的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,空对象类将无缝使用在需要检查空值的地方。
其实可以这样理解:网页发生错误,自动跳到404页面,而程序调用则自动跳转到空对象,防患于未然。

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
abstract class AbstractConsumer {
protected String name;
public abstract boolean isNull();
public abstract String getName();
}
//正常对象
class RealConsumer extends AbstractConsumer {
public RealConsumer(String name) {
this.name = name;
}
@Override
public boolean isNull() {
return false;
}
@Override
public String getName() {
return name;
}
}
//空对象
class NullConsumer extends AbstractConsumer {
public NullConsumer(String name) {
this.name = name;
}
@Override
public boolean isNull() {
return true;
}
@Override
public String getName() {
return "there's no such person";
}
}
//工厂方法
class CustomerFactory {
public static final String[] names = {"Rob", "Joe", "Julie"};
public static AbstractConsumer getCustomer(String name) {
for(int i = 0; i < names.length; i++)
if(names[i].equalsIgnoreCase(name))
return new RealConsumer(name);
return new NullConsumer(name);
}
}
public class NullObjectPatternDemo {
public static void main(String[] args) {
AbstractConsumer consumer1 = CustomerFactory.getCustomer("Rob");
AbstractConsumer consumer2 = CustomerFactory.getCustomer("Bob");
AbstractConsumer consumer3 = CustomerFactory.getCustomer("Julie");
AbstractConsumer consumer4 = CustomerFactory.getCustomer("Luara");

System.out.println("Consumer");
System.out.println(consumer1.getName());
System.out.println(consumer2.getName());
System.out.println(consumer3.getName());
System.out.println(consumer4.getName());
}
}


策略模式

策略模式定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。总而言之,策略模式就以算法为核心,在不同情况下提供不同的算法。
在这些情况时Strategy模式特别适用:

  1. 许多相关的类仅仅是行为有异 。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
  2. 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
  3. 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  4. 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

需要注意的是,如果一个系统的策略多于4个,那么就需要考虑使用混合模式,解决类膨胀的问题。
我们创建一个加减的例子来演示策略模式:

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
//抽象策略
interface Strategy {
public int doOperation(int num1, int num2);
}
class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
class OperationMinus implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
//执行策略
class SContext {
private Strategy strategy;
public SContext(Strategy stragety) {
super();
this.strategy = stragety;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
SContext context1 = new SContext(new OperationAdd());
System.out.println("1+2=" + context1.executeStrategy(1, 2));
SContext context2 = new SContext(new OperationMinus());
System.out.println("1-2=" + context2.executeStrategy(1, 2));
}
}


模板模式

模板模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按照需要重写方法实现,但调用将以抽象类中定义的方式进行。模板实际上是这样的:定义一个操作中的算法的股价,而将一些步骤延迟到子类中。可以使得子类不改变算法的结构即可重定义某些特定步骤。
当我们想要固定流程来做事情,那么模板方法非常适合我们。大致流程是这样的:首先,定义一个抽象类,定义几个抽象方法留着给子类重新实现;然后定义一个final方法来调用抽象方法,实现“模板”功能。
模板模式封装不变部分,扩展可变部分,提取了公共代码,便于维护。父类控制行为,子类具体实现。但没一个不同的实现都需要一个子类来实现,导致系统代码量增大。
需要注意的是,为了防止恶意操纵,一般模板方法都要加上final关键词。

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
abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
public final void play() {
initialize();
startPlay();
endPlay();
}
}
class Cricket extends Game {
@Override
void initialize() {
System.out.println("Cricket initialized!");
}
@Override
void startPlay() {
System.out.println("Cricket start!");
}
@Override
void endPlay() {
System.out.println("Cricket end!");
}
}
class Football extends Game {
@Override
void initialize() {
System.out.println("Football initialized!");
}
@Override
void startPlay() {
System.out.println("Football start!");
}
@Override
void endPlay() {
System.out.println("Football end!");
}
}
public class TemplatePatternDemo {
public static void main(String[] args) {
Game cricket = new Cricket();
Game football = new Football();
cricket.play();
football.play();
}
}


访问者模式

访问者模式中,我们使用了一个访问者类,它改变了元素类的执行算法。这种模式主要将数据结构和数据操作分离。
假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。
但是,访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。
访问者模式主要角色:

  1. 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
  2. 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
  3. 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
    元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
  4. 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。
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
//表示元素的接口
interface ComputerPart {
public void accept (ComputerPartVisitor computerPartVisitor);
}
//实体类
class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer() {
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++)
parts[i].accept(computerPartVisitor);
computerPartVisitor.visit(this);
}
}
//访问者的接口
interface ComputerPartVisitor {
public void visit(Monitor monitor);
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
}
//访问者
class ComputerPartDisplayVisitor implements ComputerPartVisitor {
public void visit(Monitor monitor) {
System.out.println("monitor");
}
public void visit(Computer computer) {
System.out.println("computer");
}
public void visit(Mouse mouse) {
System.out.println("mouse");
}
public void visit(Keyboard keyboard) {
System.out.println("keyboard");
}
}
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}