设计模式-观察者模式(Observer)
“在对象之间定义一个一对多的连接方法,当一个对象变换状态时,其他关联的对象都会自动收到通知。”
实现
- 主题者(Subject)、订阅者(Observer)角色;
- 建立订阅者和主题者的关系;
- 主题者发布信息时(状态改变时),通知所有订阅者;
类图
说明
- Subject - 主题接口
- 让观察者通过接口方法,订阅[Attach()]、取消订阅[Detach()]主题,这些观察者在主题内部可以使用泛型容器加以管理;
- 在主题更新时,通知所以订阅的观察者;
- ConcreteSubject - 主题具体实现
- 设置主题的内容及更新,当主题变化时,使用父类的通知方法[Notify()]告知所有订阅的观察者;
- Observer - 观察者接口
- 提供更新通知方法[Update()],让主题可以通知更新;
- ConcreteObserver - 观察者实现
- 针对主题的更新,按需求向主题获取更新状态;
代码
在Unity环境下编写运行
//需要获取主题提供的参数:用作 ‘[1]直接在方法中传递参数’ 或 ‘[2]通过在观察者中直接声明主题’两种方式的开关;
//默认为[1],使用注释符号注释掉下一行之后则变更为[2]
#define SWITCH_WAY_1
using System.Collections.Generic;
using UnityEngine;
#region UnitTest
//测试
public class Observer_UnitTest : MonoBehaviour
{
ConcreteSubject m_subject;//主题
ConcreteObserver_1 ob_1;//观察者1
ConcreteObserver_2 ob_2;//观察者2
void Awake()
{
m_subject = new ConcreteSubject();
#if SWITCH_WAY_1
ob_1 = new ConcreteObserver_1();
ob_2 = new ConcreteObserver_2();
#else
ob_1 = new ConcreteObserver_1(m_subject);
ob_2 = new ConcreteObserver_2(m_subject);
#endif
}
void Start()
{
//加入观察者
m_subject.Attach(ob_1);
m_subject.Attach(ob_2);
m_subject.SetState("testState_1");
}
}
#endregion
//观察者抽象类
public abstract class ObServer
{
public ObServer() { }
//更新方法,在主题通知更新时调用
#if SWITCH_WAY_1
// !若观察者需要获得主题提供的参数,观察者不关注主题的其他信息(观察者中未声明有主题参数)!
public abstract void Update(string state);
#else
public abstract void Update();
#endif
}
//主题抽象类
public abstract class Subject
{
public Subject() { }
protected List<ObServer> m_Observers = new List<ObServer>();//存放所有订阅的观察者
public void Attach(ObServer theObserver)//加入观察者
{
m_Observers.Add(theObserver);
}
public void Detach(ObServer theObserver)//移除观察者
{
m_Observers.Remove(theObserver);
}
#if SWITCH_WAY_1
// !若观察者需要获得主题提供的参数,观察者不关注主题的其他信息(观察者中未声明有主题参数)!
public void Notify(string state)
{
foreach (var item in m_Observers)
{
item.Update(state);
}
}
#else
public void Notify()//通知所有订阅的观察者
{
foreach (ObServer item in m_Observers)
{
item.Update();
}
}
#endif
}
//继承自抽象主题的具体主题实现
public class ConcreteSubject : Subject
{
// 本例以主题状态变化来演示在主题本身发生改变时绑定的观察者的相应(可以是其他的事件类型,当然不局限于状态改变,根据实际需求做相应改动)
string subState = string.Empty;//记录状态
//用例---
public void SetState(string newState)
{
if (string.Equals(subState, newState))
return;
subState = newState;
//执行通知订阅者(观察者)
#if SWITCH_WAY_1
Notify(subState);
#else
Notify();
#endif
}
public string GetState()
{
return string.Format("当前状态为:{0}", subState);
}
}
//继承自抽象观察者的观察者实现1
public class ConcreteObserver_1 : ObServer
{
public ConcreteObserver_1() : base() { }
#if SWITCH_WAY_1
// !若观察者需要获得主题提供的参数,观察者不关注主题的其他信息(观察者中未声明有主题参数)!
public override void Update(string state)
{
Debug.Log(string.Format("观察者1对主题的响应,并从主题中得到参数,得到状态为{0}", state));
}
#else
public ConcreteObserver_1(ConcreteSubject theSubject)
{
m_subject = theSubject;
}
ConcreteSubject m_subject;
public override void Update()
{
Debug.Log("观察者1对主题的响应,直接通过主题获得状态:" + m_subject.GetState());
}
#endif
}
public class ConcreteObserver_2 : ObServer
{
#if SWITCH_WAY_1
//add[往后内容为添加需要获取主题提供的参数]:// !若观察者需要获得主题提供的参数,观察者不关注主题的其他信息(观察者中未声明有主题参数)!
public override void Update(string state)
{
Debug.Log(string.Format("观察者2对主题的响应,并从主题中得到参数,得到状态为{0}", state));
}
#else
public ConcreteObserver_2(ConcreteSubject theSubject)
{
m_subject = theSubject;
}
ConcreteSubject m_subject;
public override void Update()
{
Debug.Log("观察者2对主题的响应,直接通过主题获得状态: " + m_subject.GetState());
}
#endif
}
使用观察者模式的优点
可以有效的解除"事件的发生“与有关的”系统功能调用“之间的绑定;
实现观察者时注意项
- 双向与单向的信息通知
观察者也可以同时是主题; - 类过多的问题
随着一个主题的观察者的增加可能会导致类过多情况的出现;
如果想要减少类的产生,可以考虑向主题注册时,不使用”类对象“,而使用”回调函数“,之后再将功能相似的”回调函数“以一个类来管理;
与其他模式相关
- 命令模式(Command)
两个模式都着重于将”发生“与”执行“消除耦合或减少依赖性;
当观察者模式中的主题只存在一个观察者时,就非常像命令模式,但还是有一些差异可以分辩出两个模式的应用时机:- 命令模式: 该模式的另一个重点是”命令的管理“,应用的系统对于发出的命令有新增、删除、记录、排序、撤销等需求;
- 观察者模式:对于”观察者”可以进行管理,可以在系统运行时间决定订阅和退订操作,让执行功能的“观察者“可以被管理;
小结
观察者模式的设计思路是,先设计一个主题(Subject),让这个主题发布时可同时通知关心这个主题的观察者(Observer),并且主题不必关心观察者接下来会执行那些操作,而且对于执行的观察者来说,还是可以动态决定是否要执行后续功能;