GoF说明:
在对象之间建立一个一对多的链接方法,当一个对象发生改变时,其他关联的对象都会自动接收到通知
模式说明:
游戏中当某个公共的数据改变之后,通常很多需要显示的地方都要做出相应的改变。如果没有设计模式,我们是不是需要一个个的去通知,并且还需要做一下空判断,看所有需要通知的对象是不是已经在场景中了,这是不好的,数据发生改变,正确的做法时我们已经通知已经在场景中并且在关注这个数据的对象就可以了。这就是观察者模式的核心,为公共数据建立一个主题,所有想观察这个主题的对象把自己注册进这个主题。但主题发生改变时,所有的观察者也做出回应
两个重要名词:
Subject:数据变化的主题接口
Observer:需要观察某个数据的观察者接口
案例分析:游戏中金币发生变化,通知UI改变显示
1:游戏通常在顶部展示自己的金币数量,当在游戏中操作获得金币后,我们应该要通知UI改变现实的数量
2.代码分析:
实现Observer抽象接口
public abstract class IGameObserver{
public virtual void SetSubject(IGameSubject subject){}
public abstract void UpdateAction(System.Object obj = null);
}
实现Subject接口
public abstract class IGameSubject{
private List<IObserver> obs = new List<IObserver>();
private System.Object mParam = null;
public void RegistObserver(IObserver ob){
if(!obs.Contains(ob)){
obs.Add(ob;)
}
}
public void RemoveObserver(IObserver ob){
if(obs.Contains(ob)){
obs.Remove(ob);
}
}
public virtual void Notify(System.Object parma){
foreach(IGameObserver observer in obs ){
//默认使用不带参数的,如果需要带参数,根据需求进行重写
observer.updateAction(parma);
}
}
public void SetParma(System.Object parma = null){
this.mParma = parma ;
Notify(parma);
}
}
实现事件ID,这里使用获得金币,获得金币之后要更新
public enum GameEventID{
NONE,
COIN
}
实现IGameEventSystem集中管理游戏的事件分发与响应
public class GameEventSystem{
private Dictionary<GameEventID,IGameSubject> mEvtDic = new Dictionary<GameEventID,IGameSubject>();
public void RegisterObserver<T>(GameEventID extId , IGameObserver observer)where T:IGameSubject {
IGameSubject subject = mEvtDic[extId] ;
if(subject == null){
//使用泛型模板,这样可以避免很多的分支结构,扩展起来也相当方便
//GameEventSystem理论上也不应该管有多少类型的Subject
subject = new T();
mEvtDic[extId] = subject ;
}
subject.RegistObserver(observer);
}
public void RemvoeObserver(GameEventID extId,IGameObserver observer){
if(mEvtDic.ContainsKey(extId ) ){
mEvtDic[extId].RemoveObserver(observer);
}
}
//这里注意一下,最好使用重载的NotifySubject函数区分有参无参,这里就不赘述了
public void NotifySubject(GameEventID extId,System.Object parma = null ){
if(mEvtDic.ContainsKey(extId )){
mEvtDic[extId].SetParma(parma)
}
}
//释放所有事件,这个方法应该再子系统的统一管理对象中进行调用
public void Release(){
mEvtDic.Clear();
}
//理论上不应该手动调用析构,但是这里不想再多加管理类做说明了
~GameEventSystem(){
Release();
}
}
具体实现,当金币改变时,我们需要再UI上更新,先实现一个金币变化主题.,方法直接使用继承类中的方法就可以了,因为没有什么需要特殊重写的地方
public class CoinSubject : IGameSubject{
}
再来实现GameUI上的观察者
public class GameUICoinObserver : IGameObserver {
public GameUI mGameUI;
public GameUICoinObserver (GameUI gameUI){
mGameUI = gameUI;
}
public void UpdateAction(System.Object obj = null){
int coinNum = Convert.ToInt32(obj);
mGameUI .UpCoinText(coinNum);
}
}
我们数据存放在PlayerData中,并且也是在PlayerData分发事件
public class PlayerData{
public int mCoin;
public void AddCoin(int num){
mCoin += num;
GameEventSystem.Instance.NotifySubject(GameEventID.COIN,mCoin);
}
}
接下来实现GameUI
public class GameUI{
public Text coinText;
//因为还要考虑到去除观察者
//更方便的方法是,再GameUI的基类中维护一个Observer数组,然后基类销毁的时候释放观察者
public GameUICoinObserver mCoinObserver;
public void Init(){
mCoinObserver = GameUICoinObserver(this);
GameEventSystem.Instance.RegisterObserver<CoinSubject>(GameEventID.COIN extId,mCoinObserver );
}
public void UpCoinText(int coinNum){
coinText.text = coinNum.ToString();
}
public void Destory(){
GameEventSystem.Instance.RemoveObserver(GameEventID.COIN extId,mCoinObserver );
}
}
至此我们就完成一个使用观察者模式实现的游戏事件系统,但是读者思考一下这里的事件系统是不是很麻烦,一个功能区域要监听多少事件,我们就要给这个功能区域创建多少个专属的观察者。虽然解决了耦合问题,但是这依旧是不合理的。那合理的做法又是什么呢,是使用委托或者匿名函数作为Observer观察者,Subject维护一个函数数组,当有事件发生的时候直接执行这个函数数组。这里是为了让读者能清晰的认识到观察者模式的工作流程,才使用常规的方式为大家解释。后面会在出一个以函数实现的观察者模式博客。