分类目录归档:设计模式

责任链设计模式的实践应用

Gof在『设计模式』中将责任链模式归为行为模式,它的目的是:

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

说得直白一点的就是,当客户端提交了一个请求后,责任链中的对象都可以处理这个请求,并且可以选择将请求继续传递下去或者就此终止。责任链中的对象只关心自己是否有能力或者有职责处理请求,客户端则只关心请求提交上去,而不在意是谁在处理,也不在意有没有人处理。

除了解耦,责任链模式能带来一个显而易见的好处就是可以通过调整责任链中对象数量及顺序来改变请求处理流程。软件工程中使用责任链设计模式的场景很常见,比如J2EE中filter和拦截器的设计,再比如一些应用程序中插件的设计。

下图是责任链模式的标准代码结构:

责任链模式

它由三部分组成:
1. Client: 向责任链提交请求。
2. Handler: 定义一个处理请求的接口和方法,此处为HandlerRequest()。
3. ConcreteHandler: Handler接口的实现类,通常有多个。可以处理请求,也可以将请求委托给链中的下一个ConcreteHandler处理。

简单介绍完责任链模式的基本概念后,现在来看看它的具体应用。自动化部署系统在执行部署任务的时候,会涉及到中央控制服务器与部署主机(Agent)之间较多的指令交互。我们采用的是一种通用协议,将具体的部署任务(包含部署、更新、回滚、启动、停止等操作)或配置文件抽象为一个个模板,中央控制服务器通过模板生成文件后下发给Agent,最终Agent直接执行脚本或者替换配置来完成部署任务。这个过程中,中央控制服务器要从不同地方获取部署任务相关的产品信息、环境信息、实例信息、版本号、部署路径、端口号等等,然后用这些信息来填充模板中的占位符。这个模板参数收集和填充的过程很适合用责任链模式来实现。

下面是实现过程和步骤:

0x00: 定义一个抽象接口Chain:

1
2
3
public interface Chain {
    public Map<String, Object> handle(Map<String, Object> params);
}

0x01: 按需实现各种ConcreteChain,这里以获取环境信息为例,实现EnvChain:

1
2
3
4
5
6
7
8
9
10
11
public class EnvChain implements Chain {
    @Override
    public Map<String, Object> handle(Map<String, Object> params) {
        long envId = (Long) params.get("envId");
        Env env = DbAction.getEnvsById(envId);
        // ......省略代码......
        Map<String, Object> dataMap = params;
        dataMap.put("env", env);
        return dataMap;
    }
}

0x02: 实现一个TemplateHolder类,用于责任链对象的注册和暴露请求提交接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TemplateHolder {
    private List<Chain> chainList = new ArrayList<Chain>();
 
    // 注册链对象。
    public boolean register(final Chain chain) {
        chainList.add(chain);
        return true;
    }
 
    // 暴露请求提交接口,用于客户端提交模板参数填充请求。
    public ScriptObject exec(Map<String, Object> params) {
        Map<String, Object> dataMap = new HashMap<String, Object>();
        // 根据链对象的注册顺序,依次调用对象的handle()方法处理。
        for (Chain c : chainList) {
            dataMap.putAll(c.handle(params));
        }
        String file = Merger.mergeTemplate(dataMap, tmpl);
        // ......省略代码......
    }
}

0x03: 客户端提交请求,返回一个合成好的文件。

1
2
3
4
5
6
7
TemplateHolder holder = TemplateHolder.getHolder();
// 注册链对象 
holder.register(new EnvChain()); 
Map<String, Object> params = new HashMap<String, Object>();
params.put("envId", envId);
// 客户端发出请求,最终模板在ScriptObject对象中返回。
ScriptObject so = holder.exec(params);

使用责任链模式可以为模板文件生成提供很大的灵活性和代码重用率,因为大部分模板都是需要产品、环境和实例相关的信息的,因此基本上每个ProductChain,EnvChain和InsChain都是可用重用的。另外,自动化部署系统采用的责任链模式并没有严格遵循其定义,因为所有的链中对象都负责对请求进行了部分处理,而不是某个对象单独承担。从形式上看,我们的实现同观察者模式有点类似,但有一个核心区别:观察者模式中的观察者角色之间没有依赖关系,互相独立;但在我们的场景里,链对象之间存在状态共享,比如在InsChain对象中查询实例信息,得到环境ID后再传递给EnvChain进行后续处理。

--EOF--

利用JDK实现观察者模式

JDK自1.0开始就支持观察者模式,java.util包提供了观察者模式结构中的Subject(被观察者,目标)和Observer(观察者)的定义和实现,分别对应java.util.Observable类和java.util.Observer接口。

Observable类已实现的方法包括注册观察者addObserver、移除观察者deleteObserver、设置状态改变标记setChanged,以及通知观察者类刷新数据的notifyObservers方法。Observer接口声明了update方法,当接到通知时用于刷新数据。

以下是利用Observable类和Observer接口实现的观察者模式:
1. 观察者ConcreteObserver类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.edu.zju.dp.observer;
 
import java.util.Observable;
import java.util.Observer;
 
/**
 * @author Feng
 * @version $Id: ConcreteObserver.java, v 1.0 2013-2-28 下午07:54:56
 */
public class ConcreteObserver implements Observer {
 
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Observerable: " + ((ConcreteObservable) o).getName() 
                + ", Params: " + arg);
    }
}

2. 被观察者(目标)ConcreteObservable类

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
package cn.edu.zju.dp.observer;
 
import java.util.Observable;
 
/**
 * @author Feng
 * @version $Id: ConcreteObservable.java, v 1.0 2013-2-28 下午07:58:00
 */
public class ConcreteObservable extends Observable {
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName1(String name) {
        this.name = name;
        setChanged(); //设置状态变更标记
        notifyObservers(); //通知所有观察者。
    }
 
    public void setName2(String name) {
        this.name = name;
        setChanged(); //设置状态变更标记
        notifyObservers(name);//通知所有观察者,同时向所有观察者传递自定义消息。
    }
}

3. 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.edu.zju.dp.observer;
 
/**
 * @author Feng
 * @version $Id: ObserverTest.java, v 1.0 2013-2-28 下午08:01:29
 */
public class ObserverTest {
 
    public static void main(String[] args) {
        ConcreteObserver observer = new ConcreteObserver();
        ConcreteObservable observable = new ConcreteObservable();
        //注册观察者,当observable状态更新时,observer会收到通知。
        observable.addObserver(observer);
 
        observable.setName1("foo");//输出:Observerable: foo, Params: null
        observable.setName2("bar");//输出:Observerable: bar, Params: bar
    }
}

运行结果如下:

1
2
Observerable: foo, Params: null
Observerable: bar, Params: bar

被观察者ConcreteObservable类数据更新(setName)后,调用setChanged设置内部标记,表示状态更新(此时调用基类的hasChanged方法返回true),同时调用notifyObservers方法通知注册过的观察者。观察者类收到通知后,调用update方法刷新数据。update方法有类型分别为Observerable和Object的两个参数,前者是发出通知的被观察者(目标)类的引用,后者方便被观察者类向观察者发送通知时指定参数。可以理解为:Observerable参数支持观察者类从被观察者类“拉”数据,Object参数支持被观察者类向观察者类“推”数据。

JDK为什么把Observable设计成类,Observer设计成接口呢?知乎上有一个问答(『Java 的jdk中 为什么Observer 是接口,Observable 是类?』)靠谱:为所有被观察者对象设置内部状态变更标记,注册、移除、通知观察者的方法是相同的,将它们封装成类可以极大简化编写观察者模式程序,用户只需在Observable的子类中根据业务逻辑需要合理调用上述方法即可。而对于观察者而言,update方法中实现的是具体业务逻辑,例如可能刷新图表、更新表单数据、启动定时任务等,因此必须作为一个接口提供,由用户实现。

--EOF--

设计模式 —— Observer(观察者模式)

Observer模式(观察者模式)在GoF归纳的23种设计模式里还算是比较容易理解的一种,它适用于一种一对多的环境下,"一"和"多"之间存在依赖关系,当"一"的状态发生改变时,依赖于它的"多"都会得到通知并自动更新自己。Observer模式的对象有两类,一类为目标,就是上述关系中的"一",另一类为观察者,是上述关系中的"多"。

观察者模式的结构图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 +-----------+ obervers                 +------------+
 |  Subject  |------------------------->|  Observer  |
 +===========+                          +============+
 |  attach() |   +------------------+   |  Update()  |
 |  detach() |   |foreach observers{|   +------^-----+
 |  notify() |-->|   o->Updata()    |          |
 +-----^-----+   |}                 |          |
       |         +------------------+ +----------------+
       |                              |ConcreteObserver|   +--------------------+
+---------------+             subject +================+   |observerState =     |
|ConcreteSubject|<--------------------|   Update()     |-->| subject->GetState()|
+===============+                     | observerState  |   +--------------------+
|  GetStatus()  |                     +----------------+
|  SetStatus()  |
+---------------+

图1 观察者模式结构图
(Download ASCII Picture)

如图所示,观察者模式的参与者分为4类:
1、Subject(目标)
Suject至少需提供3个接口,Attach()和Detach()用于注册和解除观察者对象。Notify()用于在状态改变时通知所有观察者对象。
2、Observer(观察者)
Observer至少需要提供一个Update()接口,用于更新具体观察者对象的状态。
3、ConcreteSubject(具体目标)
ConcreteSubject除了必须实现Subject接口中的方法之外,还必须提供一个"注册中心",所有观察者对象都必须在这个注册中心进行注册,方便在目标状态发生改变时受到通知。
4、ConcreteObserver(具体观察者)
ConcreteObserver维护着一个自身的状态,该状态与ConcreteSubject中的状态要一致,要达到这个目的,还要维护一个指向ConcreteSubject的引用。

观察者模式几个常见的应用场景比如Web开发中的MVC架构,Model作为一个目标(Subject),View组件就是观察者(Observer),一个Model可能会对应多个View组件,因此当Model中数据更新时就通过观察者模式来通知View,从而使得各个View能保持数据的一致性。另一个应用场景如GoF中举例的图形用户界面工具箱。它将数据和界面分离,当数据有更新时,多个界面(表格,柱状图,饼图)均能得到更新。此外,C#中的委托机制也采用了观察者设计模式,委托给一个delegate类的函数就是观察者模式中的Observer角色,delegate类通过"+="和"-="运算符来注册和解除观察者对象,参见『C#的委托和事件』

以下的程序是一个Observer模式的简单实现,其功能与GoF中举例的GUI工具箱类似,目标类为一个数据源(DataSourceMng),观察者类有三个:TableGraph、PieGraph、BarGraph分别表示不同的数据展示方式:表格,饼图,柱状图。数据源中的数据可由数据源本身修改,也可由观察者类进行修改,但无论是谁对其进行修改,目标类都有责任调用Notify()通知向它注册过的所有观察者发送通知。特别当观察者类修改数据源数据时,要注意这个观察者类自身的状态需要由目标类的Notify()统一进行修改,而不是直接擅自进行修改,这也是出于保证数据一致性的重要考虑。

1、Suject类

1
2
3
4
5
6
7
package cn.edu.zju.dp.observer;
 
public interface Subject {
    public void Attach(Observer o);
    public void Detach(Observer o);
    public void Notify();
}

2、Observer类

1
2
3
4
5
6
package cn.edu.zju.dp.observer;
 
public interface Observer {
    public void Updata();
    public void QueryData();
}

3、ConcreteSuject类

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
package cn.edu.zju.dp.observer;
 
import java.util.LinkedList;
import java.util.List;
 
public class DataSourceMng implements Subject {
    private double               rate = 0.0;
    private final List<Observer> list = new LinkedList<Observer>();
 
    public void setRate(double rate) {
        this.rate = rate;
        Notify();
    }
 
    public double getRate() {
        return this.rate;
    }
 
    @Override
    public void Attach(Observer o) {
        list.add(o);
    }
 
    @Override
    public void Detach(Observer o) {
        list.remove(o);
    }
 
    @Override
    public void Notify() {
        for (Observer o : list) {
            o.Updata();
        }
    }
}

4、ConcreteObserver类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TableGraph implements Observer {
    private final Subject datasource;
    private double        rate;
 
    public TableGraph(Subject datasource) {
        this.datasource = datasource;
    }
 
    @Override
    public void Updata() {
        this.rate = ((DataSourceMng) datasource).getRate();
    }
 
    @Override
    public void QueryData() {
        System.out.println("TableGraph's Rate = " + rate);
    }
 
    public void ModifyRate(double rate) {
        ((DataSourceMng) datasource).setRate(rate);
    }
}

因PieGraph和BarGraph的实现与TableGraph完全一致,故不重复贴出。

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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.observer;
 
/**
 * 
 * @author 衔山
 * @version $Id: ObserverPatternTest.java, v 0.1 2011-10-23 下午03:01:49
 */
public class ObserverPatternTest {
    public static void main(String[] args) {
        Subject datasource = new DataSourceMng();
 
        Observer table = new TableGraph(datasource);
        Observer pie = new PieGraph(datasource);
        Observer bar = new BarGraph(datasource);
 
        //在目录类中注册观察者类
        datasource.Attach(table);
        datasource.Attach(pie);
        datasource.Attach(bar);
 
        //观察者类修改数据源
        ((TableGraph) table).ModifyRate(0.75);
 
        //所有观察者类的状态得到修改
        table.QueryData(); //TableGraph's Rate = 0.75
        pie.QueryData(); //PieGraph's Rate = 0.75
        bar.QueryData(); //BarGraph's Rate = 0.75
    }
}

Reference:
[1] GoF. 设计模式:可复用面向对象软件的基础[M], 机械工业出版社, 2006-07.

--EOF--

设计模式 —— State(状态模式)

stateState模式的设计意图是允许一个对象能根据其内部状态的不同而改变它的行为。或者说,对象所表现的行为取决于它当前的状态。

一个典型的例子是有限状态机(Finite State Machine, FSM),FSM响应事件后所表现出的行为完全由它当时所处的状态决定。实际中要实现一个FSM,往往最先想到的是嵌套if…else…和switch语句,根据当前状态选择不同的if…else…分支,然后根据事件在switch分支中进行处理。这种方法可行,但是当状态和事件非常多时,这样的实现方法就显得效率低下,也不容易维护和扩展,这时候,就要用到State设计模式。

State模式的结构图如下:

上图中,Context为客户关注的接口,它维护着一个指向State接口的实例(ConcreteStateX)的引用。State是状态接口,封装了Context在不同状态下表现的行为。ConcreteState是State接口的具体实现类,每一个ConcreteState类实现了Context在某种状态下的具体行为。Context把与状态相关的请求委托给了ConcrateState类,同时,它也要将自身作为一个参数传递给这个ConcreateState类,这样,该ConcrateState类才能访问到Context中的方法。简单讲,就是Context本来有很多种状态,然后表现得行为也随着状态的不同而不同,现在,Context把不同状态下的行为分散到各个ConcreateState类中,如此一来,Context只需维护一个表示着当前状态的ConcreteState类,就能自如地在各种状态下的展现不同的行为。

下面就是用State模式实现的一个TCP有限状态机。TCP有限状态机的转移图如下所示,它共有11种状态,每一种状态的下一个状态完全由收到的事件决定,因此,假如用判断语句来实现,就会非常繁琐。

1、TCPConnection类,State模式的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
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * State模式的Context类
 * @author 衔山
 * @version $Id: TCPConnection.java, v 0.1 2011-9-10 下午04:48:24
 */
public class TCPConnection {
 
    private TCPState state;
 
    //初始TCP状态为CLOSED
    public TCPConnection() {
        this.state = ClosedTCPState.closedState;
    }
 
    //以下为一些列动作(行为)方法
    public void passiveOpen() {
        state.passiveOpen(this);
    }
 
    public void rcvSYNsendSYNACK() {
        state.rcvSYNsendSYNACK(this);
    }
 
    public void rcvACK() {
        state.rcvACK(this);
    }
 
    public void rcvFINsendACK() {
        state.rcvFINsendACK(this);
    }
 
    public void sendFIN() {
        state.sendFIN(this);
    }
 
    public void activeOpen() {
        state.activeOpen(this);
    }
 
    public void rcvSYNACKsendACK() {
        state.rcvSYNACKsendACK(this);
    }
 
    public void MSLtimeout() {
        state.MSLtimeout(this);
    }
 
    public void close() {
        state.close(this);
    }
 
    public void rcvRST() {
        state.rcvRST(this);
    }
 
    public void sendSYN() {
        state.sendSYN(this);
    }
 
    public void rcvFINACKsendACK() {
        state.rcvFINACKsendACK(this);
    }
 
    //状态转移方法
    public void setState(TCPState state) {
        this.state = state;
    }
 
    /**
     * 静态测试方法
     * @param args
     */
    public static void main(String[] args) {
        TCPConnection conn = new TCPConnection();
 
        //服务器端workflow(TCP有限状态图中的蓝色虚线部分)
        System.out.println("服务器端workflow:");
        conn.passiveOpen();
        conn.rcvSYNsendSYNACK();
        conn.rcvACK();
        conn.rcvFINsendACK();
        conn.sendFIN();
        conn.rcvACK();
 
        System.out.println("===================================");
        //客户端workflow(TCP有限状态图中的红色实线部分)
        System.out.println("客户端workflow:");
        conn.sendSYN();
        conn.rcvSYNACKsendACK();
        conn.sendFIN();
        conn.rcvACK();
        conn.rcvFINsendACK();
        conn.MSLtimeout();
    }
}

2、TCPState 接口类,State模式的State角色。每个TCPState中的操作都有一个TCPConnection类的实例,这样TCPState就能访问和改变TCPConnection中的状态和数据。

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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * State模式的State接口
 * @author 衔山
 * @version $Id: TCPState.java, v 0.1 2011-9-10 下午04:38:40
 */
public interface TCPState {
    public void passiveOpen(TCPConnection conn);
 
    public void rcvSYNsendSYNACK(TCPConnection conn);
 
    public void rcvACK(TCPConnection conn);
 
    public void rcvFINsendACK(TCPConnection conn);
 
    public void sendFIN(TCPConnection conn);
 
    public void activeOpen(TCPConnection conn);
 
    public void rcvSYNACKsendACK(TCPConnection conn);
 
    public void MSLtimeout(TCPConnection conn);
 
    public void close(TCPConnection conn);
 
    public void rcvRST(TCPConnection conn);
 
    public void sendSYN(TCPConnection conn);
 
    public void rcvFINACKsendACK(TCPConnection conn);
}

3、以下11个类为TCP的状态类,State模式中得ConcreateState角色。它们没有自己的状态(属性),只能通过动作(方法)去改变TCPConnection类的状态。

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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * CLOSED状态类
 * @author 衔山
 * @version $Id: ClosedTCPState.java, v 0.1 2011-9-10 下午10:22:32
 */
public class ClosedTCPState implements TCPState {
    public static ClosedTCPState closedState = new ClosedTCPState();
 
    @Override
    public void passiveOpen(TCPConnection conn) {
        conn.setState(ListenTCPState.listenState);
        System.out.println("From ClosedTCPState to ListenTCPState");
    }
 
    @Override
    public void sendSYN(TCPConnection conn) {
        conn.setState(SynSentTCPState.synsentState);
        System.out.println("From ClosedTCPState to SynSentTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * CLOSE_WAIT状态类
 * @author 衔山
 * @version $Id: CloseWaitTCPState.java, v 0.1 2011-9-10 下午10:32:43
 */
public class CloseWaitTCPState implements TCPState {
    public static CloseWaitTCPState closewaitState = new CloseWaitTCPState();
 
    @Override
    public void sendFIN(TCPConnection conn) {
        conn.setState(LastAckTCPState.lastackState);
        System.out.println("From CloseWaitTCPState to LastAckTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * CLOSING状态类
 * @author 衔山
 * @version $Id: ClosingTCPState.java, v 0.1 2011-9-10 下午10:34:01
 */
public class ClosingTCPState implements TCPState {
    public static ClosingTCPState closingState = new ClosingTCPState();
 
    @Override
    public void rcvACK(TCPConnection conn) {
        conn.setState(TimeWaitTCPState.timewaitState);
        System.out.println("From ClosingTCPState to TimeWaitTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * ESTABLISHED状态类
 * @author 衔山
 * @version $Id: EstablishedTCPState.java, v 0.1 2011-9-10 下午10:32:06
 */
public class EstablishedTCPState implements TCPState {
    public static EstablishedTCPState establishedState = new EstablishedTCPState();
 
    @Override
    public void rcvFINsendACK(TCPConnection conn) {
        conn.setState(CloseWaitTCPState.closewaitState);
        System.out.println("From EstablishedTCPState to CloseWaitTCPState");
    }
 
    @Override
    public void sendFIN(TCPConnection conn) {
        conn.setState(FinWait1TCPState.finwait1State);
        System.out.println("From EstablishedTCPState to FinWait1TCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * FIN_WAIT_1状态类
 * @author 衔山
 * @version $Id: FinWait1TCPState.java, v 0.1 2011-9-10 下午10:33:22
 */
public class FinWait1TCPState implements TCPState {
    public static FinWait1TCPState finwait1State = new FinWait1TCPState();
 
    @Override
    public void rcvACK(TCPConnection conn) {
        conn.setState(FinWait2TCPState.finwait2State);
        System.out.println("From FinWait1TCPState to FinWait2TCPState");
    }
 
    @Override
    public void rcvFINACKsendACK(TCPConnection conn) {
        conn.setState(TimeWaitTCPState.timewaitState);
        System.out.println("From FinWait1TCPState to TimeWaitTCPState");
    }
 
    @Override
    public void rcvFINsendACK(TCPConnection conn) {
        conn.setState(ClosingTCPState.closingState);
        System.out.println("From FinWait1TCPState to ClosingTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * FIN_WAIT_2状态类
 * @author 衔山
 * @version $Id: FinWait2TCPState.java, v 0.1 2011-9-10 下午10:35:15
 */
public class FinWait2TCPState implements TCPState {
    public static FinWait2TCPState finwait2State = new FinWait2TCPState();
 
    @Override
    public void rcvFINsendACK(TCPConnection conn) {
        conn.setState(TimeWaitTCPState.timewaitState);
        System.out.println("From FinWait2TCPState to TimeWaitTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * LAST_ACK状态类
 * @author 衔山
 * @version $Id: LastAckTCPState.java, v 0.1 2011-9-10 下午10:34:39
 */
public class LastAckTCPState implements TCPState {
    public static LastAckTCPState lastackState = new LastAckTCPState();
 
    @Override
    public void rcvACK(TCPConnection conn) {
        conn.setState(ClosedTCPState.closedState);
        System.out.println("From LastAckTCPState to ClosedTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * LISTEN状态类
 * @author 衔山
 * @version $Id: ListenTCPState.java, v 0.1 2011-9-10 下午10:24:15
 */
public class ListenTCPState implements TCPState {
 
    public static ListenTCPState listenState = new ListenTCPState();
 
    @Override
    public void close(TCPConnection conn) {
        conn.setState(ClosedTCPState.closedState);
        System.out.println("From ListenTCPState to ClosedTCPState");
    }
 
    @Override
    public void rcvSYNsendSYNACK(TCPConnection conn) {
        conn.setState(SynRcvdTCPState.synrcvdState);
        System.out.println("From ListenTCPState to SynRcvdTCPState");
    }
 
    @Override
    public void sendSYN(TCPConnection conn) {
        conn.setState(SynSentTCPState.synsentState);
        System.out.println("From ListenTCPState to SynSentTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * SYN_RCVD状态类
 * @author 衔山
 * @version $Id: SynRcvdTCPState.java, v 0.1 2011-9-10 下午10:25:14
 */
public class SynRcvdTCPState implements TCPState {
    public static SynRcvdTCPState synrcvdState = new SynRcvdTCPState();
 
    @Override
    public void rcvACK(TCPConnection conn) {
        conn.setState(EstablishedTCPState.establishedState);
        System.out.println("From SynSentTCPState to EstablishedTCPState");
    }
 
    @Override
    public void rcvRST(TCPConnection conn) {
        conn.setState(ListenTCPState.listenState);
        System.out.println("From SynSentTCPState to ListenTCPState");
    }
 
    @Override
    public void sendFIN(TCPConnection conn) {
        conn.setState(FinWait1TCPState.finwait1State);
        System.out.println("From SynSentTCPState to FinWait1TCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * SYN_SENT状态类
 * @author 衔山
 * @version $Id: SynSentTCPState.java, v 0.1 2011-9-10 下午10:31:17
 */
public class SynSentTCPState implements TCPState {
    public static SynSentTCPState synsentState = new SynSentTCPState();
 
    @Override
    public void close(TCPConnection conn) {
        conn.setState(ClosedTCPState.closedState);
        System.out.println("From SynSentTCPState to ClosedTCPState");
    }
 
    @Override
    public void rcvSYNACKsendACK(TCPConnection conn) {
        conn.setState(EstablishedTCPState.establishedState);
        System.out.println("From SynSentTCPState to EstablishedTCPState");
    }
 
    @Override
    public void rcvSYNsendSYNACK(TCPConnection conn) {
        conn.setState(SynRcvdTCPState.synrcvdState);
        System.out.println("From SynSentTCPState to SynRcvdTCPState");
    }
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}
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
/**
 * 衔山的博客 - http://fengchj.com
 * Copyright (c) 2011 All Rights Reserved.
 */
package cn.edu.zju.dp.state;
 
/**
 * TIME_WAIT状态类
 * @author 衔山
 * @version $Id: TimeWaitTCPState.java, v 0.1 2011-9-10 下午10:36:04
 */
public class TimeWaitTCPState implements TCPState {
    public static TimeWaitTCPState timewaitState = new TimeWaitTCPState();
 
    @Override
    public void MSLtimeout(TCPConnection conn) {
        conn.setState(ClosedTCPState.closedState);
        System.out.println("From TimeWaitTCPState to ClosedTCPState");
    }
 
    @Override
    public void activeOpen(TCPConnection conn) {
        System.out.println("Wrong Sequense");
    }
 
    //以下省略大量默认的接口方法实现,详见本文附件中的源代码。
    //......
}

上述程序的测试方法为,在TCPConnection起一个Main方法,根据TCP的工作逻辑有次序调用TCPConnection类的各种动作(方法),如例中所示,控制台输出结果应为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
服务器端workflow:
From ClosedTCPState to ListenTCPState
From ListenTCPState to SynRcvdTCPState
From SynSentTCPState to EstablishedTCPState
From EstablishedTCPState to CloseWaitTCPState
From CloseWaitTCPState to LastAckTCPState
From LastAckTCPState to ClosedTCPState
===================================
客户端workflow:
From ClosedTCPState to SynSentTCPState
From SynSentTCPState to EstablishedTCPState
From EstablishedTCPState to FinWait1TCPState
From FinWait1TCPState to FinWait2TCPState
From FinWait2TCPState to TimeWaitTCPState
From TimeWaitTCPState to ClosedTCPState

如果调用了与TCP有限状态机逻辑不同的动作,那么控制台会输出"Wrong Sequence"。

完整程序详见附件(右键->另存为...,然后将.txt改为.zip后解压):附件

State模式分离了FSM的逻辑和动作,动作放在了Context类(例子中的TCPConnection类)中,逻辑分布在State接口(例子中的TCPState接口)的各个实现类(例子中的11个状态类)中,使得逻辑和动作能独立变化,相互之间不影响。当增加一个状态时,只需派生一个State接口,而不必修改Context类。至于State模式的缺陷,大家也看到了,写TCP有限状态机的11个状态类已经相当吃力,重复性劳动很多。而且,代码较为分散,本来用判断语句在一个类中可以实现的功能现在分散在了13个类中,较难快速看清FSM的状态转移逻辑。

Reference:
[1] GoF. 设计模式:可复用面向对象软件的基础[M], 机械工业出版社, 2006-07.
[2] Robert C. Martin 著, 邓辉 译. 敏捷软件开发原则、模式与实践[M], 清华大学出版社, 2009-09.

--EOF--

设计模式 —— Bridge(桥接模式)

类的继承使得子类具有基类的属性和方法,从而在很大程度上实现了代码重用。但是,继承关系会导致子类和基类之间建立了强耦合关系,当基类改变时,子类必须也跟着改变。桥接模式中,软件的抽象化和实现化使用了组合关系。相对于继承关系,组合关系是一种弱耦合关系,因此桥接模式可以让类的抽象化和实现化相对独立变化。GoF对桥接模式意图的定义就是:将抽象部分和它的实现部分分离,使它们可以独立地变化[1]

桥接模式的结构图如下所示:

其中,Abstract是系统抽象化部分的接口,它维护着一个实现化部分的接口Implementor的引用,Implementor接口与Abstract接口可以相同也可以不同,一般而言,Implementor声明一些较底层的接口,而Abstract声明的接口处于较高层次,要通过调用Implementor提供的接口来实现。RefinedAbstraction类扩展Abstract接口,提供Abstract接口的具体实现。ConcreteImplementorA和ConcreteImplementorB实现了Implementor接口。

网上关于桥接模式最多的例子就是设计日志系统。假设某个日志系统待记录的日志种类包含业务日志、异常日志、外部接口调用日志,而日志的存储形式可能是数据库、文本文件、XML文件。这样,这个日志系统就有两个维度的变化,一个维度是日志种类,另一个维度是日志存储形式。如果采用继承关系设计这个日志系统中的必须设计实现3*3共9个类,而且当一个维度增加一项功能时,需要增加另一个维度长度个数(本例中是3个)的类。当采用桥接模式时,因为采用了组合关系,所以只需设计3+3共6个类,任一维度新增一项功能,只需新增一个类即可。

本例中将日志种类看成桥接模式中的抽象化部分,日志的存储形式看成实现化部分。代码如下:
1、抽象化部分接口类(结构图中的Abstract类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: Log.java, v 0.1 2011-8-28 下午10:24:18
 */
public abstract class Log {
    protected LogStoreMethod logStoreMethod;
 
    Log(LogStoreMethod logStoreMethod) {
        this.logStoreMethod = logStoreMethod;
    }
 
    /**
     * 客户调用接口
     */
    public abstract void makeLog();
}

2、实现化部分接口类(结构图中的Implementor类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: LogStoreMethod.java, v 0.1 2011-8-28 下午10:27:06
 */
public abstract class LogStoreMethod {
    public abstract void store();
}

3、存储介质为数据库时的实现类(结构图中的ConcreteImplementor类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: DbStoreMethod.java, v 0.1 2011-8-28 下午10:28:40
 */
public class DbLogStoreMethod extends LogStoreMethod {
 
    /** 
     * @see com.alipay.bridge.LogStoreMethod#store()
     */
    @Override
    public void store() {
        System.out.println("Store via Database");
    }
}

4、存储介质为文本文件时的实现类(结构图中的ConcreteImplementor类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: FileStoreMethod.java, v 0.1 2011-8-28 下午10:30:38
 */
public class FileLogStoreMethod extends LogStoreMethod {
 
    /** 
     * @see com.alipay.bridge.LogStoreMethod#store()
     */
    @Override
    public void store() {
        System.out.println("Store via File");
    }
}

5、存储介质为XML文件时的实现类(结构图中的ConcreteImplementor类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: XMLStoreMethod.java, v 0.1 2011-8-28 下午10:29:31
 */
public class XMLLogStoreMethod extends LogStoreMethod {
 
    /** 
     * @see com.alipay.bridge.LogStoreMethod#store()
     */
    @Override
    public void store() {
        System.out.println("Store via XML");
    }
}

6、业务日志实现类(结构图中的RefinedAbstraction类)

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
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: BizLog.java, v 0.1 2011-8-28 下午10:41:37
 */
public class BizLog extends Log {
    BizLog(LogStoreMethod logStoreMethod) {
        super(logStoreMethod);
    }
 
    /** 
     * @see com.alipay.bridge.Log#makeLog()
     */
    @Override
    public void makeLog() {
        System.out.print("make Biz Log...");
        this.logStoreMethod.store();
    }
}

7、外部接口调用日志实现类(结构图中的RefinedAbstraction类)

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
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: ServiceLog.java, v 0.1 2011-8-28 下午10:43:30
 */
public class ServiceLog extends Log {
    ServiceLog(LogStoreMethod logStoreMethod) {
        super(logStoreMethod);
    }
 
    /** 
     * @see com.alipay.bridge.Log#makeLog()
     */
    @Override
    public void makeLog() {
        System.out.print("make Service Log...");
        this.logStoreMethod.store();
    }
}

8、异常日志实现类(结构图中的RefinedAbstraction类)

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
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: ExceptionLog.java, v 0.1 2011-8-28 下午10:38:12
 */
public class ExceptionLog extends Log {
    ExceptionLog(LogStoreMethod logStoreMethod) {
        super(logStoreMethod);
    }
 
    /** 
     * @see com.alipay.bridge.Log#makeLog()
     */
    @Override
    public void makeLog() {
        System.out.print("make Exception Log...");
        this.logStoreMethod.store();
    }
}

9、客户调用代码(结构图中的Client类)

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
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.alipay.bridge;
 
/**
 * 
 * @author changjian.feng
 * @version $Id: Client.java, v 0.1 2011-8-28 下午10:44:46
 */
public class Client {
 
    /**
     * 
     * @param args
     */
    public static void main(String[] args) {
        /**
         * Exception Log
         */
        Log exXMLLog = new ExceptionLog(new XMLLogStoreMethod());
        exXMLLog.makeLog();
 
        Log exDBLog = new ExceptionLog(new DbLogStoreMethod());
        exDBLog.makeLog();
 
        Log exFileLog = new ExceptionLog(new FileLogStoreMethod());
        exFileLog.makeLog();
 
        /**
         * Biz Log
         */
        Log bizXMLLog = new BizLog(new XMLLogStoreMethod());
        bizXMLLog.makeLog();
 
        Log bizDBLog = new BizLog(new DbLogStoreMethod());
        bizDBLog.makeLog();
 
        Log bizFileLog = new BizLog(new FileLogStoreMethod());
        bizFileLog.makeLog();
 
        /**
         * Service Log
         */
        Log serviceXMLLog = new ServiceLog(new XMLLogStoreMethod());
        serviceXMLLog.makeLog();
 
        Log serviceDBLog = new ServiceLog(new DbLogStoreMethod());
        serviceDBLog.makeLog();
 
        Log serviceFileLog = new ServiceLog(new FileLogStoreMethod());
        serviceFileLog.makeLog();
 
    }
}

Client类的执行结果如下:

1
2
3
4
5
6
7
8
9
make Exception Log...Store via XML
make Exception Log...Store via Database
make Exception Log...Store via File
make Biz Log...Store via XML
make Biz Log...Store via Database
make Biz Log...Store via File
make Service Log...Store via XML
make Service Log...Store via Database
make Service Log...Store via File

桥接模式(Bridge)拥有着非常好的扩展性。假如我们想扩展这个日志系统,比如增加一种日志存储形式为控制台输出,那么只要新增一个扩展了LogStoreMethod抽象类的ConsoleLogStoreMethod类,再在业务日志、异常日志和外部接口调用日志等要输出到控制台的地方将ConsoleLogStoreMethod类地实例传到它们的构造方法里即可。反之,如果增加一种日志种类为错误日志,那么也只要新增一个扩展Log抽象类的ErrorLog类,这个错误日志要采用哪种存储方式,只需把改存储方式类的实例当成参数传给构造方法就可以了。

Reference:
[1] GoF. 设计模式:可复用面向对象软件的基础[M], 机械工业出版社, 2006-07. 
[2] 赵春霞,宫明明. 桥接模式在日志系统中的应用[J], 青岛职业技术学院学报, 2010.2, 23(1):57-59.

--EOF--