在日常生活中,交通信号灯指挥者日益拥挤的城市交通。红灯亮,汽车停止;绿灯亮,汽车继续前行;在这个过程中,交通信号灯是汽车的观察目标,而汽车则是观察者。随着交通信号灯的变化,汽车的行为也会随之变化,一盏交通信号灯可以指挥多辆汽车。在软件系统中,有些对象之间也存在类似交通信号灯和汽车之间的关系,一个对象的状态或行为的变化将会导致其他对象的状态或者行为也发生改变,它们之间将产生联动,正所谓牵一发而动全身。为了更好地描述对象之间存在的这种一对多的联动,观察者模式应运而生。

观察者模式(Chain of Responsibility) 学习难度:★★★☆☆ 使用频率:★★★★★

一、多人联机对战游戏的设计

需求背景:M公司欲开发一款多人联机对战游戏,在游戏中,多个游戏玩家可以加入同一战队组成联盟,当战队中某一成员收到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将作出响应。M公司开发人员需要提供一个设计方案来实现战队成员之间的联动。

  M公司开发人员通过分析,发现在该系统中战队成员之间的联动过程可以简单描述如下:

  联盟成员收到攻击 => 发送通知给盟友 => 盟友作出响应

  如果每个联盟成员都需要持有盟友的信息才能及时通知每一位盟友,因此这样系统开销较大。因此,M公司开发人员决定引入一个新角色“战队控制中心”来负责维护和管理每个战队所有成员的信息,如下图所示:

  如何实现对象之间的联动?让我们来看看观察者模式吧。

二、观察者模式概述

2.1 观察者模式简介

  观察者模式是一种使用频率最高的设计模式之一,用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。

观察者(Observer)模式:定义对象之间的一种一对多依赖关系,使得当每一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。  

2.2 观察者模式结构

  观察者模式包含以下4个角色:

  (1)Subject(抽象目标):又称为主题,是被观察的对象。

  (2)ConcreteSubject(具体目标):抽象目标的子类,通常包含有经常发生改变的数据,当它的状态发生改变时,向其各个观察者发出通知。

  (3)Observer(抽象观察者):观察者将对观察目标的改变做出反应。

  (4)ConcreteObserver(具体观察者):具体观察者中维持一个指向具体目标对象的引用,它用于存储具体观察者的有关状态,这些状态需要和具体目标地状态保持一致。

三、重构多人联机对战游戏

3.1 重构后的设计结构

  其中,AllyControlCenter充当抽象目标,ConcreteAllyControlCenter则充当具体目标,IObserver充当抽象观察者,Player则充当具体观察者。

3.2 具体代码实现

  (1)抽象观察者:IObserver

    /// <summary>
/// 抽象观察类 - IObserver接口
/// </summary>
public interface IObserver
{
string Name { get; set; }
void Help(); // 声明支援盟友的方法
void BeAttacked(AllyControlCenter acc); // 声明遭受攻击的方法
}

  (2)具体观察者:Player

    /// <summary>
/// 具体观察者类:战队成员
/// </summary>
public class Player : IObserver
{
public string Name
{
get;
set;
} public void BeAttacked(AllyControlCenter acc)
{
Console.WriteLine("{0}:我正被攻击,速来援救!", this.Name);
// 调用战队控制中心类的通知方法来通知盟友
acc.NotifyObserver(this.Name);
} public void Help()
{
Console.WriteLine("{0} :坚持住,立马来救你!", this.Name);
}
}

  (3)抽象目标:AllyControlCenter

    /// <summary>
/// 抽象目标类:战队控制中心
/// </summary>
public abstract class AllyControlCenter
{
public string AllyName { get; set; }
protected IList<IObserver> playerList = new List<IObserver>(); public void Join(IObserver observer)
{
playerList.Add(observer);
Console.WriteLine("通知:{0} 加入 {1} 战队", observer.Name, this.AllyName);
} public void Quit(IObserver observer)
{
playerList.Remove(observer);
Console.WriteLine("通知:{0} 退出 {1} 战队", observer.Name, this.AllyName);
} // 声明抽象通知方法
public abstract void NotifyObserver(string name);
}

  (4)具体目标:ConcreteAllyControlCenter

    public class ConcreteAllyControlCenter : AllyControlCenter
{
public ConcreteAllyControlCenter(string allyName)
{
Console.WriteLine("系统通知:{0} 战队组建成功!", this.AllyName);
Console.WriteLine("-------------------------------------------------------");
this.AllyName = allyName;
} // 实现通知方法
public override void NotifyObserver(string playerName)
{
Console.WriteLine("通知:盟友们,{0} 正遭受敌军攻击,速去抢救!", playerName);
foreach (var player in playerList)
{
if (!player.Name.Equals(playerName, StringComparison.OrdinalIgnoreCase))
{
player.Help();
}
}
}
}

  (5)客户端测试

    public class Program
{
public static void Main(string[] args)
{
// Step1.定义观察者对象
AllyControlCenter acc = new ConcreteAllyControlCenter("金庸群侠");
// Step2.定义4个观察者对象
IObserver playerA = new Player() { Name = "杨过" };
acc.Join(playerA);
IObserver playerB = new Player() { Name = "令狐冲" };
acc.Join(playerB);
IObserver playerC = new Player() { Name = "张无忌" };
acc.Join(playerC);
IObserver playerD = new Player() { Name = "段誉" };
acc.Join(playerD);
// Step3.当某盟友遭受攻击
playerA.BeAttacked(acc); Console.ReadKey();
}
}

  编译调试结果如下图所示:

  

  在本实例中,实现了两次对象之间的联动:Player.BeAttacked() => AllyControlCenter.NotifyObserver() => Player.Help()

四、观察者模式与MVC

  在当前流行的MVC(Model-View-Controller)结构中也应用了观察者模式,它包含了3个角色:模型、视图和控制器。其中,模型可对应观察者模式中的观察目标,而视图则对应于观察者,控制器充当二者之间的中介者。当模型层的数据发生改变时,视图将会发出数据请求以期改变其显示内容,如下图所示:

五、观察者模式总结

5.1 主要优点

  (1)可以实现表示层和数据逻辑层的分离 => 各种不同的表示层可以充当具体观察者

  (2)支持广播通信,观察目标会向已注册的观察者对象发送通知 => 简化一对多系统设计的难度

  (3)增加新的观察者无须修改原有系统代码 => 满足开闭原则

5.2 主要缺点

  (1)如果一个观察目标有很多直接和间接的观察者 => 所有观察者收到通知会花费大量时间

  (2)如果观察者和观察目标之间存在循环依赖 => 可能导致系统崩溃

5.3 应用场景

  (1)一个抽象模型有两个方面,其中一个方面依赖于另一个方面 => 封装起来使其独立改变和复用

  (2)一个对象的改变将导致一个或多个其他对象也发生改变,但并不知道具体有多少个对象将要发生改变 => 最熟悉的陌生人

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—15.观察者(Observer)模式的更多相关文章

  1. 设计模式C++描述----04.观察者(Observer)模式

    一. 概述 Observer 模式要解决的问题为:建立一个一(Subject)对多(Observer)的依赖关系,并且做到当“一”变化的时候,依赖这个“一”的多也能够同步改变. Sbuject 相当于 ...

  2. 《Head First 设计模式》ch.2 观察者(Observer)模式

    观察者模式 定义了对象之间一对多以来,这样一来,当一个对象改变状态时,它所有的依赖者都会收到通知并自动更新 设计原则-松耦合 松耦合将对象之间的互相依赖降到了最低——只要他们之间的接口仍被遵守 观察者 ...

  3. Java 实现观察者(Observer)模式

    1. Java自带的实现 类图 /** * 观察目标 继承自 java.util.Observable * @author stone * */ public class UpdateObservab ...

  4. Java设计模式之从[星际争霸的兵种升级]分析观察者(Observer)模式

    观察者模式定义对象的一种一对多的依赖关系.当一个对象的状态发生改变时.全部依赖于它的对象都会得到通知并被自己主动更新. 一个简单的样例是.在星际争霸的虫族中有一个0基础单位叫做跳狗(Zergling) ...

  5. 观察者(Observer)模式

    观察者模式又叫做发布-订阅模式(Publish.Subscribe)模式.模型-视图模式(Model/View)模式.源-监听器模式(Source/Listener)模式或从属者(Dependents ...

  6. 《图解设计模式》读书笔记8-1 Observer模式

    目录 示例程序 程序类图 程序 角色和类图 角色 类图 思路拓展 可复用性 Observer的顺序 MVC模式 Observer模式 Observer模式即观察者模式,该模式中,被观察者的状态发生变化 ...

  7. 面向对象设计模式——观察者(OBSERVER)模式

    定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.  Observer模式描述了如何建立这种关系.这一模式中的关键对象是目标(subject ...

  8. 设计模式之观察者(OBSERVER)模式

    定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.  Observer模式描述了如何建立这种关系.这一模式中的关键对象是目标(subject ...

  9. Head First 设计模式 —— 02. 观察者 (Observer) 模式

    思考题 在我们的一个实现中,下列哪种说法正确?(多选) P42 public class WeatherDate { // 实例变量声明 public void measurementsChanged ...

随机推荐

  1. 解决-Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HO问题

    原因:因为你的编译工具(eclipse/Myeclipse...)没有添加jdk.添加M2_HOME的环境变量. 解决: ①:window->Preferences->java->I ...

  2. Go 单例模式[个人翻译]

    原文地址:http://marcio.io/2015/07/singleton-pattern-in-go/ 最近几年go语言的增长速度非常惊人,吸引着各界人士切换到Go语言.最近有很多关于使用Rub ...

  3. Java打印常见图形

    package org.other; import java.util.Scanner; /* * 题目:打印常用图形逻辑 */ public class Ninthninth { public st ...

  4. canvas+gif.js打造自己的数字雨头像

    前言 昨天 是1024程序员节,不知道各位看官过的怎么样.既然是过节,就要有个过节的样子,比方说,换个头像

  5. Golang开发者常见的坑

    Golang开发者常见的坑 目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Accid ...

  6. 如何利用Oracle VM Templates 在几分钟内部署Oracle Real Application Clusters (RAC)

    本文未经授权,禁止一切形式的转载.如果对本文有任何疑问可以通过以下方式和我交流: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiang ...

  7. C++ 对象成员函数(非静态方法)

    1.神奇的inline语法与语义 inline语义C99和C++98都有.之前在单源文件编写的时候一直没有发现问题,但是一考虑到多文件的链接,就发现矛盾了. 一些inline的原则: 1. inlin ...

  8. Python 之简单线程池创建

    try: from Queue import Queue, Empty except: from queue import Queue, Empty import threading import t ...

  9. Windows下mysql5.5主从同步

    前置条件: A主机(作为主服务器) 环境:Win7,mysql5.5 IP:172.17.42.82 B主机(作为从服务器) 环境:Win7,mysql5.5 IP:172.17.42.156 主服务 ...

  10. Android的主线程和子线程

    在一个Android 程序开始运行的时候,会单独启动一个Process.默认的情况下,所有这个程序中的Activity或者Service(Service和 Activity只是Android提供的Co ...