
我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Windows环境下如何编程实现观察者模式(设计模式)。
不知道大家有没有这样的感觉,看了一大堆编程和设计模式的书,却还是很难理解设计模式,无从下手。为什么?因为你看的都是理论书籍。
我今天就在Windows操作系统上安装好JAVA的IDE编程工具,并用JAVA语言来实现一个观察者模式,真实的实现一个,你看懂代码后,自然就明白了。
观察者模式Observer Pattern(行为型设计模式)
定义:对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新。
上面定义听懂了吗?莫名其妙看不懂对吧。所以我们还是来看看实现生活中的例子。
比如说一场足球比赛,两队正踢的非常焦灼,互有攻守。这时其中的A队的队长给全体队员做了一个暗示,所以前峰和前卫队员心领神会,趁对方队员向前冲的时候,突然让中场队员发起了一次突然进攻。很快他们就利用这次机会进球了。

在软件系统中对象并不是孤立存在的,一个对象行为的改变可能会导致一个或多个其他与之存在依赖关系的对象行为发生改变,就像一支足球队的队长和队员一样。观察者模式用于描述对象之间的依赖关系,为实现多个对象之间的联动提供了一种解决方案,它是一种使用频率非常高的设计模式。随着球队队长的指挥的变化,队员的行为也将随之变化,队长一人可以指挥多名队员。
在软件系统中有些对象之间也存在类似队长和队员之间的关系,一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动。为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式就逐渐形成了,它定义了对象之间的一种一对多的依赖关系,让一个对象的改变能够影响其他对象。一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
这里有一个有趣的现象,很多程序员从业十多年了也没有听说过什么观察者模式,这是为什么?其实是因为大家叫法不同,观察者模式还有其他两种叫法,比如发布-订阅(Publish-Subscribe)模式和模型-视图(Model-View)模式。观察者模式是一种对象行为型模式。我相信发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式这两种大多数JSP、PHP、ASP.NET程序员都知道,因为很多MVC开发其实就是在运用这种模式。
观察者模式结构中通常包括观察目标和观察者两个继承层次结构,其结构如下。

观察者模式包含Subject(目标)、ConcreteSubject(具体目标)、Observer(观察者)、ConcreteObserver(具体观察者)等4个角色。目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意的观察者来观察,它提供方法来增加和删除观察者对象,同时它定义了通知方法。具体目标是目标类的子类,它通常包含有经常发生改变的数据,当它的状态发生改变时将向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法。观察者将对观察目标的改变作出反应,观察者一般定义为接口,该接口声明了更新数据的方法。在具体观察者中维护一个指向具体目标对象的引用它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致,它实现了在抽象观察者Observer中定义的 update()方法。
观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态,以使其状态与目标状态同步。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。下面通过代码进行进一步分析。
JAVA代码实现
import java.util.*;
public abstract class Subject {
//定义一个观察者集合用于存储所有观察者对象
protected ArrayList observers<Observer>= new ArrayList();
//注册方法,用于向观察者集合中增加一个观察者
public void attach(Observer observer){
observers.add(observer);
}
//注销方法,用于从观察者集合中删除一个观察者
public void detach(Observer observer){
observers.remove(observer);
//声明抽象通知方法
public abstract void notify();
}
public class ConcreteSubject extends subject
//实现通知方法
public void notify(){
//遍历观察者集合,调用每一个观察者的响应方法
for(Object obs: observers){
((Observer)obs). update();
}
}
}
public interface Observer {
//声明响应方法
public void update();
}
public class ConcreteObserver implements Observer{
//实现响应方法
public void update(){
/具体响应代码
}
}
在有些更加复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类 ConcreteSubject中的状态(属性),因此在 ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject 实例,通过该实例获取存储在 ConcreteSubject中的状态。如果ConcreteObserver的 update()方法不需要使用到ConcreteSubject中的状态属性,则在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。
应用实例

大家应该打过CS对战游戏吧,我以CS1.5为例,在这个游戏中,多个玩家可以加入同一战队组成队伍,当战队中的某一成员受到敌人攻击时将给所有其他队友发送通知,队友收到通知后将作出响应。我们这里使用观察者模式设计并实现该过程,以实现战队队员之间的联动。
但这里我要声明一点,我们这个观察者模式设计出来是给CS1.5里面的机器人用的,不是给真实的游戏玩家用。因为真实的玩家自己会作判断下一步该做什么,因为是人控制的,所以有些玩家怕被敌人打,早就溜之大吉,哪里还顾得上队友。我这里是给游戏里游戏自动生成的机器人用,机器人比较遵守团队规则,一旦发现队员受到攻击,马上会去支援。(在CS游戏里,有时人数凑不到对战人数,有的玩家会用CS的快捷键生成一些机器人陪自己打游戏,这些机器人会自动找敌对目标攻击,也会救队友)
CS1.5系统中战队成员之间的联动过程可以描述如下:队员受到攻击→发送通知给队友→队友作出响应。如果按照上述思路来设计系统,一个战队队员在受到攻击时需要通知他的每一位队友,每个队员都需要知道其他所有队友的信息,因此可以引入一个角色——“队员生存情况看板”来负责维护和管理每个战队中所有成员的信息。当一个队员受到攻击时将向生存情况看板发送信息,生存情况列表可通知到每个队友,队友再作出响应。
(1)Panel: 队员生存情况看板类,充当抽象目标类。
package designpatterns. observer;
import java.util.*;
public abstract class Panel {
protected String zdName; //CS战队名称
//定义一个集合用于存储战队成员
protected ArrayList <Observer> players = new ArrayList <Observer>();
public void setzdName(String zdName){
this. zdName= zdName;
}
public String getzdName(){
return this. zdName;
}
//注册方法
public void join(observer obs){
players. add(obs);
}
/注销方法
public void quit(Observer obs){
players.remove(obs);
}
//声明抽象通知方法
public abstract void notifyObserver(String name);
}
(2)AlivePanel:具体队员生存情况看板类,充当具体目标类。
package designpatterns. observer;
public class AlivePanel extends Panel {
public AlivePanel(String zdName){
this. zdName= zdName;
}
//实现通知方法
public void notifyObserver(String name){
System.out.println(this. zdName+name+"受到攻击!");
//遍历观察者集合,调用每一个队友的营救方法
for(object obs:players){
if (!((Observer)obs).getName().equalsIgnoreCase(name)){
((Observer)obs). rescue();
}
}
}
}
(3)Observer:抽象观察者类。
package desigmpatterns. observer;
public interface Observer {
public String getName();
public void setName(String name);
public void rescue(); //声明营救队友方法
public void beAttackec(Panel cp); //声明遭受攻击方法
}
(4)Player:战队成员类,充当具体观察者类。
package designpatterns. observer;
public class Player implements Observer{
private String name;
public Player(String name){
this. name = name;
}
public void setName(String name){
this. name= name;
}
public String getName(){
return this. name;
}
//支援盟友方法的实现
public void rescue()(
System.out.println("先躲好,我"+this. name+"来了");
}
//遭受攻击方法的实现,当遭受攻击时将调用队员生存情况列表类的通知方法notifyObserver()
//来通知盟友
public void beAttacked(Panel cp)(
System.out.println(this.name +"被攻击");
cp.notifyObserver(name);
}
}
(5)Client:客户端测试类。
package designpatterns. observer;
public class Client{
public static void main(String args[]){
/定义观察目标对象
Panel cp;
cp = new AlivePanel("CS战队");
//定义4个观察者对象
Observer player1, player2, player3, player4;
player1= new Player("战士1");
cp. join(playerl);
player2= new Player("战士2");
cp. join(player2);
player3= new Player("战士3");
cp. join(player3);
player4= new Player("战士4");
cp. join(player4);
//某成员遭受攻击
player1.beAttacked(cp);
}
}
输出结果如下:
战士1被攻击
CS战队战士1受到攻击
先躲好,我战士2来了
先躲好,我战士3来了
先躲好,我战士4来了
在本实例中实现了两次对象之间的联动,当一个游戏玩家Player对象的beAttacked()方法被调用时将调用队员生存情况看板Panel的 notifyObserver()方法进行处理,而在notifyObserver()方法中又将调用其他 Player 对象的 rescue()方法。 Player的 beAttacked()方法,Panel的notifyObserver()方法以及 Player的 rescue()方法构成了一个联动效应,执行顺序用我自创的画图法来画就是如下这样:

各位小伙伴,这次我们就说到这里,下次我们再深入研究windows环境下的各类设计模式实现。
作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。