1、引言
在我们平时的软件开发中,观察者模式是我们常用的一种模式。在现实生活中,观察者模式也是处处可见。例如:微信中的订阅号,博客的订阅和微博中的关注好友,这些都属于观察者模式的应用。下面是一些我对观察者模式的理解。
2、观察者模式详细介绍
2.1、定义
- 观察者模式(Observer Pattern)
观察者模式又叫做发布-订阅(Publoish/Subscribe)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
2.2、解决的问题
一个对象状态改变通知其他对象的问题,而且要考虑到易用和低耦合,保证高度的协作。
2.3、模式的原理
下面时该模式的UML类图:
通过上图我们不难发现,观察者模式中的角色如下:
- 抽象主题角色(Subject):抽象主题把所有观察者对象的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
- 抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
- 具体主题角色(ConcreteSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
- 具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。
2.4、类图的实现
下面我们来实现类图中的代码:
观察者抽象类:
/// <summary>
/// 抽象观察者类
/// </summary>
abstract class Observer
{
/// <summary>
/// 为所有观察者定义接口(更新接口,接到通知时更新自己)
/// </summary>
public abstract void Update();
}
抽象主题类:
/// <summary>
/// 抽象主题(抽象通知者)类
/// </summary>
abstract class Subject
{
private IList<Observer> observers = new List<Observer>();
/// <summary>
/// 增加观察者
/// </summary>
/// <param name="observer"></param>
public void Attach(Observer observer)
{
observers.Add(observer);
}
/// <summary>
/// 移除观察者
/// </summary>
/// <param name="observer"></param>
public void Detach(Observer observer)
{
observers.Remove(observer);
}
/// <summary>
/// 通知所有的观察者
/// </summary>
public void Notify()
{
foreach (Observer observer in observers)
{
observer.Update();
}
}
}
具体观察者:
/// <summary>
/// 具体主题(具体通知者)
/// 该类中存在一个状态,改状态变化时通知所有的观察者
/// </summary>
class ConcreteSubject : Subject
{
private string subjectState;
public string SubjectState
{
get { return subjectState; }
set { subjectState = value; }
}
}
具体观察者类:
/// <summary>
/// 具体观察者
/// 实现抽象观察者的所有接口,以便于本省状态和通知者状态协调
/// </summary>
class ConcreteObserver : Observer
{
private string name;
private string observerState;
private ConcreteSubject subject;
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}
public ConcreteObserver(ConcreteSubject subject,string name)
{
this.subject = subject;
this.name = name;
}
public override void Update()
{
observerState = subject.SubjectState;
Console.WriteLine("观察者{0}的新状态是:{1}", name, observerState);
}
}
测试一下:
//具体通知者
ConcreteSubject s = new ConcreteSubject();
s.Attach(new ConcreteObserver(s, "XX"));
s.Attach(new ConcreteObserver(s, "YY"));
s.Attach(new ConcreteObserver(s, "ZZ"));
s.SubjectState = "ABC";
s.Notify();
测试结果如下:
观察者XX的新状态是:ABC
观察者YY的新状态是:ABC
观察者ZZ的新状态是:ABC
通过对类图的实现我想你已经对观察模式的原理已经理解了,下面我们再来举例说明一下,来加深理解。
2.5、C#举例(委托实现)
- 背景:老板回到公司,同事们都在努力工作,看NBA的张三、聊天的李四发现情况后立即停下与工作无关的事,继续上班。
我们平时在写一个观察者模式时,可能我们的类中也许并不是这个更新方法满足上面的接口。也就是说抽象通知者依赖抽象观察者这个接口不存在,或者具体观察者没有实现这个更新方法,那么这个通知就无法完成了。此时我们可以使用事件委托实现。下面我们看具体代码:
通知者接口(可能我们这个类只是要具有通知的功能,可能不需要实现这个类,此时用接口比较合适):
/// <summary>
/// 通知者
/// </summary>
interface Notify
{
/// <summary>
/// 通知方法
/// </summary>
void Notify();
/// <summary>
/// 属性
/// <summary>
string SubjectState
{
get;
set;
}
}
这里先声明一个委托:
/// <summary>
/// 定义一个委托,用来通知更新
/// </summary>
delegate void EventHandler();
老板类:
class Boss : Notify
{
/// <summary>
/// 声明一个类型为委托EventHandler的事件,名为Update,
/// </summary>
public event EventHandler Update;
private string action;
public string SubjectState
{
get { return action; }
set { action = value; }
}
public void Notify()
{
Update();
}
}
看NBA的同事:
/// <summary>
/// (看NBA的)观察者
/// </summary>
class ObserverNBA
{
private string name;
private Notify notify;
public ObserverNBA(string name, Notify notify)
{
this.name = name;
this.notify = notify;
}
public void CloseNBAView()
{
Console.WriteLine("{0},{1}关闭NBA直播,继续工作!", notify.SubjectState, name);
}
}
聊天的同事:
/// <summary>
/// (聊天的)观察者
/// </summary>
class ObserverChat
{
private string name;
private Notify notify;
public ObserverChat(Notify notify,string name)
{
this.name = name;
this.notify = notify;
}
public void StopChat()
{
Console.WriteLine("{0},{1}停止聊天,开始工作!",notify.SubjectState,name);
}
}
测试一下:
//通知者
Boss huHanSan = new Boss();
//观察者
ObserverNBA tsNBA = new ObserverNBA("张三", huHanSan);
ObserverChat tsChat = new ObserverChat(huHanSan, "李四");
//委托事件(更新方法)
huHanSan.Update += new EventHandler(tsNBA.CloseNBAView);
huHanSan.Update += new EventHandler(tsChat.StopChat);
huHanSan.SubjectState = "我胡汉三又回来了";
//通知者发起通知
huHanSan.Notify();
测试结果:
我胡汉三又回来了,张三关闭NBA直播,继续工作!
我胡汉三又回来了,李四停止聊天,开始工作!
通过以上代码,我想,您已对观察者模式已经有了更深入的理解。下面我们来看看观察者的优缺点。
3、观察者模式优缺点
优点:
- 观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
- 观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
- 观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。
缺点:
- 如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
- 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
- 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。
4、观察者模式适用场景
在下面的情况下可以考虑使用观察者模式:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。
- 当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。
- 当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。
5、应用举例(unity)
在unity应用中,使用C#中的事件委托Delegate来彻底解除通知者和观察者之间的耦合。
- 关于委托:
委托是一种函数指针。一旦为委托分配了方法,委托将与该方法有相同的行为。委托方法可以像其它任何方法一样,具有参数和返回值。委托可以看作是对函数(方法)的的抽象,是特殊的“函数类”,委托的实例代表一个(或多个)具体的函数,它可以是多路广播的。 - 关于事件:
事件基于委托,为委托提供了一种发布/订阅机制。事件的订阅与取消与我们刚才讲的观察者模式中的订阅与取消类似,只是表现形式有所不同。在观察者模式中,订阅使用方法Attach()来进行;在事件的订阅中使用“+=”。类似地,取消订阅在观察者模式中用Dettach(),而事件的取消用“-=”。
下面是一个小栗子,仅用来抛砖引玉,不足之处还请见谅。
首先准备UI:
下面是通知者:
using UnityEngine;
using System.Collections;
using System;
using UnityEngine.UI;
/// <summary>
/// 委托通知的方法类型
/// </summary>
public delegate void ObserverTest();
/// <summary>
/// 这个脚本是事件的派发类,
/// 所有发生的事件都由这个类来派发
/// </summary>
public class Subject : MonoBehaviour
{
public static event ObserverTest ObserverTestEvent;
void Start()
{
Button btn = transform.Find("Button").GetComponent<Button>();
btn.onClick.AddListener(onClick);
}
public void onClick()
{
if (ObserverTestEvent != null)
ObserverTestEvent();
}
}
接着写几个观察者:
观察者1:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
/// <summary>
/// (按钮)观察者
/// </summary>
public class ButtonObserver : MonoBehaviour
{
void Start()
{
Subject.ObserverTestEvent += ChangeButton;
}
public void ChangeButton()
{
Button btn = transform.Find("Button (1)").GetComponent<Button>();
Image image = btn.GetComponent<Image>();
Text txt = btn.transform.Find("Text").GetComponent<Text>();
image.color = Color.red;
txt.text = "On Change Button Text";
}
void OnDestroy()
{
Subject.ObserverTestEvent -= ChangeButton;
}
}
观察者2:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
/// <summary>
/// (文本)观察者
/// </summary>
public class TextObserver_1 : MonoBehaviour
{
// Use this for initialization
void Start()
{
Subject.ObserverTestEvent += ChangeText;
}
private void ChangeText()
{
Text txt = transform.Find("Text1").GetComponent<Text>();
txt.text = "hi, Eagle1";
}
void OnDestroy()
{
Subject.ObserverTestEvent -= ChangeText;
}
}
观察者3:
using System.Collections;
using UnityEngine.UI;
using UnityEngine;
public class TextObserver_2 : MonoBehaviour
{
// Use this for initialization
void Start()
{
Subject.ObserverTestEvent += ChangeText;
}
private void ChangeText()
{
Text txt = transform.Find("Text2").GetComponent<Text>();
txt.text = "hi, Eagle2";
}
void OnDestroy()
{
Subject.ObserverTestEvent -= ChangeText;
}
}
如上图所示把三个观察者都挂在Canvas上,点击左边按钮,效果如下:
6、总结
到这里,观察者模式的分享就介绍了。观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。
7、unity工程下载
在文章的最后我们给出上述例子的工程下载链接,方便朋友们探讨交流!本次演示版本Unity5.6.3f1(64-bit),VS2015需要下载的朋友,请点击这里下载。
The End
好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!
喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!
相关阅读
C# 23种设计模式(unity演示)