• 观察者模式的介绍


    设计模式原则最终的落脚点:优化依赖关系,改善这种抵御变化的能力,使用面向对象改善这种情况,遵循依赖倒置原则。

    模型–动机–意图–结构

    一、引言

    在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题。

    在这里插入图片描述
    在这里插入图片描述

    二、 观察者模式的介绍

    2.1 观察者模式的定义

    从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。

    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。

    在这里插入图片描述

    2.2 观察者模式的结构

    从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中
    1、首先会存在两个对象,一个是观察者对象,另一个就是主题对象
    2、然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。
    3、理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。
    在这里插入图片描述
    抽象后、优化依赖关系如上图
    有了上面的分析,下面观察者的结构图也就很容易理解了。具体结构图如下所示:
    在这里插入图片描述

    可以看出,在观察者模式的结构图有以下角色:

    • 抽象主题角色(Subject):抽象主题把所有观察者对象的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
    • 抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
    • 具体主题角色(ConcreteSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
    • 具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。

    2.3 观察者模式的实现

    在这里插入图片描述

    下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。
    这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。有了这个分析,自然实现的代码如下所示:

     // 腾讯游戏订阅号类
     public class TenxunGame
     {
        // 订阅者对象
         public Subscriber Subscriber {get;set;}
    
         public String Symbol {get; set;}
    
         public string Info {get ;set;}
    
        public void Update()
         {
           if (Subscriber != null)
           {
             // 调用订阅者对象来通知订阅者
            Subscriber.ReceiveAndPrintData(this);
           }
         }
       }
    
       // 订阅者类
      public class Subscriber
      {
         public string Name { get; set; }
    	 public Subscriber(string name)
    	{
    		 this.Name = name;
    	 }
    
    	 public void ReceiveAndPrintData(TenxunGame txGame)
    	 {
    		Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tx
    		Game.Symbol, txGame.Info);
    	 }
     }
    
    // 客户端测试
    class Program
     {
    	 static void Main(string[] args)
    	 {
    	 // 实例化订阅者和订阅号对象
    		 Subscriber LearningHardSub = new Subscriber("LearningHard");
    		 TenxunGame txGame = new TenxunGame();
    
    		 txGame.Subscriber = LearningHardSub;
    		 txGame.Symbol = "TenXun Game";
    	 	 txGame.Info = "Have a new game published ....";
    
    		 txGame.Update();
    
    		 Console.ReadLine();
    	}
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    上面代码确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:

    • TenxunGame类和Subscriber类之间形成了一种双向依赖关系,即TenxunGame调用了Subscriber的ReceiveAndPrintData方法,而Subscriber调用了TenxunGame类的属性。这样的实现,如果有其中一个类变化将引起另一个类的改变。
    • 当出现一个新的订阅者时,此时不得不修改TenxunGame代码,即添加另一个订阅者的引用和在Update方法中调用另一个订阅者的方法。

    上面的设计违背了“开放——封闭”原则,显然,这不是我们想要的。
    在这里插入图片描述
    抽象后、优化依赖关系如上图*

    对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。
    对此,我们可以做这样的思考——

    订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类。

    这样也就可以完美解决上面代码存在的问题了,具体的实现代码为:

     // 订阅号抽象类
     public abstract class TenXun
     {
    	 // 保存订阅者列表
    	 private List<IObserver> observers = new List<IObserver>();
    	 public string Symbol { get; set; }
    	 public string Info { get; set; }
    	 public TenXun(string symbol, string info)
    	{
    		 this.Symbol = symbol;
    		 this.Info = info;
    	 }
    
     #region 新增对订阅号列表的维护操作
    	 public void AddObserver(IObserver ob)
    	 {
    		observers.Add(ob);
    	 }
    	public void RemoveObserver(IObserver ob)
    	 {
    		 observers.Remove(ob);
    	}
    	 #endregion
    
    	 public void Update()
    	 {
    	 // 遍历订阅者列表进行通知
    		 foreach (IObserver ob in observers)
    		 {
    			 if (ob != null)
    			 {
    			 ob.ReceiveAndPrint(this);
    			 }
    		}
    	 }
     }
    	 // 具体订阅号类
    	 public class TenXunGame : TenXun
    	 {
    		 public TenXunGame(string symbol, string info)
    		: base(symbol, info)
    		{
    	 	}
    	 }
    
    	// 订阅者接口
    	 public interface IObserver
    	 {
    		 void ReceiveAndPrint(TenXun tenxun);
    	 }
    
    	// 具体的订阅者类
    	 public class Subscriber : IObserver
    	 {
    		 public string Name { get; set; }
    		 public Subscriber(string name)
    		 {
    			 this.Name = name;
    		 }
    
    		 public void ReceiveAndPrint(TenXun tenxun)
    		{
    		 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, te
    		nxun.Symbol, tenxun.Info);
    		 }
    	 }
    
    	 // 客户端测试
    	 class Program
    	 {
    		 static void Main(string[] args)
    		 {
    			TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
    
    	     // 添加订阅者
    		 tenXun.AddObserver(new Subscriber("Learning Hard"));
    		 tenXun.AddObserver(new Subscriber("Tom"));
    
    		 tenXun.Update();
    
    		 Console.ReadLine();
    		 }
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    上面代码是我们进行重构后的实现,重构后的代码实现类图如下所示

    在这里插入图片描述
    从上图可以发现,这样的实现就是观察者模式的实现。这样,在任何时候,只要调用了TenXun类的Update方法,它就会通知所有的观察者对象,同时,可以看到,观察者模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。这里并不是直接给出观察者模式的实现,而是通过一步步重构的方式来引出观察者模式的实现,相信通过这个方式,大家可以更深刻地理解观察者模式所解决的问题和带来的好处。

    三、.NET 中观察者模式的应用

    在.NET中,我们可以使用委托与事件来简化观察者模式的实现,上面的例子用事件和委托的实现如下代码所示:

     namespace ObserverInNET
     {
    	 class Program
    	 {
          // 委托充当订阅者接口类
           public delegate void NotifyEventHandler(object sender);
    
         // 抽象订阅号类
          public class TenXun
          {
         	  public NotifyEventHandler NotifyEvent;
    
    		  public string Symbol { get; set; }
     		  public string Info { get; set; }
     		  public TenXun(string symbol, string info)
    	    	 {
    			  this.Symbol = symbol;
    			  this.Info = info;
     		  }
     
     			 #region 新增对订阅号列表的维护操作
    		  public void AddObserver(NotifyEventHandler ob)
     			{
    			  NotifyEvent += ob;
    			 }
    		  public void RemoveObserver(NotifyEventHandler ob)
    		  {
     			  NotifyEvent ‐= ob;
      			}
    
    		 #endregion
    
     	 public void Update()
    	 {
    		 if (NotifyEvent != null)
    		 {
    			 NotifyEvent(this);
    		 }
    	 }
     }
    
    	 // 具体订阅号类
    	public class TenXunGame: TenXun
    	 {
    		 public TenXunGame(string symbol, string info)
    		 : base(symbol, info)
    		 {
    		}
    	}
    
     // 具体订阅者类
     public class Subscriber
     {
     public string Name { get; set; }
     public Subscriber(string name)
     {
     this.Name = name;
     }
    
     public void ReceiveAndPrint(Object obj)
     {
    	 TenXun tenxun = obj as TenXun;
    
    	 if (tenxun != null)
    	 {
    		 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
    	}
    }
    }
    
     static void Main(string[] args)
     {
    	 	TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
    	    	Subscriber lh = new Subscriber("Learning Hard");
    		 Subscriber tom = new Subscriber("Tom");
    
    	  // 添加订阅者
    	  tenXun.AddObserver(new NotifyEventHandler(lh.ReceiveAndPrint));
    	  tenXun.AddObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
     
    	  tenXun.Update();
    
    	 Console.WriteLine("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
     	 Console.WriteLine("移除Tom订阅者");
         tenXun.RemoveObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
         tenXun.Update();
    
        Console.ReadLine();
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    从上面代码可以看出,使用事件和委托实现的观察者模式中,减少了订阅者接口类的定义,此时,.NET中的委托正式充到订阅者接口类的角色。使用委托和事件,确实简化了观察者模式的实现,减少了一个IObserver接口的定义。

    四、观察者模式的适用场景

    在下面的情况下可以考虑使用观察者模式:

    • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。从方面的这个词中可以想到,观察者模式肯定在AOP(面向方面编程)中有所体现,更多内容参考:Observern Pattern in AOP.
    • 当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。
    • 当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。

    五、观察者模式的优缺点

    5.1 观察者模式有以下几个优点:

    • 观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
    • 观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
    • 观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。

    5.2 观察者也存在以下一些缺点:

    • 如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
    • 虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
    • 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。

    六 总结

    到这里,观察者模式的分享就介绍了。观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。

    七 观察者和 发布-订阅、事件驱动 对比

    再比如现在很多的消息中间件,RabbitMQ,kafka等,都有发布订阅功能,当一个消息发布后,所有订阅这个消息的队列都会收到这个消息,然后消费者负责去消费这些消息。
    当然不能简单的将发布-订阅模式与观察者模式混为一谈,发布-订阅模式更像是观察者模式的扩展,不过概念上还是非常相似的。
    发布-订阅模式中,订阅方可能并不知道发布方是谁,发布方也不需要同步的将状态通知到所有订阅方,他们之间的联系需要消息代理中间件来建立。所以发布-订阅的耦合更低,更利于扩展。
    ————————————————

    在这里插入图片描述
    上文中提到了观察者模式和发布——订阅模式,我们来总结一下两者差异:

    6.1 差异比较

    在这里插入图片描述

    • 在观察者模式中,观察者是知道Subject的,Subject记录了所有的观察者。
      然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

    • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

    • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。

    • 而发布-订阅模式大多数时候是异步的(使用消息队列)。

    • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

    • 尽管它们之间有区别,但有些人可能会说发布-订阅模式是观察者模式的变异,因为它们概念上是相似的。

      在观察者模式中的Subject就像一个发布者(Publisher),观察者(Observer)就是订阅者(Subscriber)。
      Subject通知观察者(Observer)就像发布者(Publisher)通知他的订阅者(Subscriber)。
      所以很多书和文章使用发布-订阅概念来解释观察者设计模式。
      但是这里还有另外一个流行的模式叫做发布-订阅设计模式。
      它的概念和观察者模式非常类似。最大的区别是:

    在发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者(Subscriber)。

    意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。
    那么如何过滤消息的呢?事实上这里有几个过程,最流行的方法是:基于主题以及基于内容。

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    6.2 细节差异比较

    那么任务订阅功能中有两类主体:

    宗门任务大殿

    • 维护拥有订阅权限的弟子列表
    • 提供弟子购买订阅权限的功能
    • 发布对应任务后通知有订阅权限的弟子
    • 接受任务通知的弟子们

    上面宗门任务大殿与弟子间的关系其实就构成了一个观察者模式。

    在观察者模式中,只有两种主体:

    目标对象 (Object) 和 观察者 (Observer)。宗门任务大殿就是目标对象,弟子们就是观察者。

    目标对象 Subject:

    • 维护观察者列表 observerList ———— 维护拥有订阅权限的弟子列表
    • 定义添加观察者的方法 ———— 提供弟子购买订阅权限的功能
    • 当自身发生变化后,通过调用自己的 notify 方法依次通知每个观察者执行 update 方法 ————
      发布对应任务后通知有订阅权限的弟子

    观察者 Observer 需要实现 update 方法,供目标对象调用。
    update方法中可以执行自定义的业务逻辑 ———— 弟子们需要定义接收任务通知后的方法,例如去抢任务或任务不适合,继续等待下一个任务

    我们把上面的文字形象化一下:

    在这里插入图片描述

    class Observer {
        constructor(name) {
            this.name = name;
        }
        update({taskType, taskInfo}) {
            // 假设任务分为日常route和战斗war
            if (taskType === "route") {
                console.log(`${this.name}不需要日常任务`);
                return;
            }
            this.goToTaskHome(taskInfo);
            
        }
        goToTaskHome(info) {
            console.log(`${this.name}去任务大殿抢${info}任务`);
        }
    }
    
    class Subject {
        constructor() {
            this.observerList = []
        }
        addObserver(observer) {
            this.observerList.push(observer);
        }
        notify(task) {
            console.log("发布五星任务");
            this.observerList.forEach(observer => observer.update(task))
        }
    }
    
    const subject = new Subject();
    const stu1 = new Observer("弟子1");
    const stu2 = new Observer("弟子2");
    
    // stu1 stu2 购买五星任务通知权限
    subject.addObserver(stu1);
    subject.addObserver(stu2);
    
    // 任务殿发布五星战斗任务
    const warTask = {
        taskType: 'war',
        taskInfo: "猎杀时刻"
    }
    
    // 任务大殿通知购买权限弟子
    subject.notify(warTask);
    
    // 任务殿发布五星日常任务
    const routeTask = {
        taskType: 'route',
        taskInfo: "种树浇水"
    }
    
    subject.notify(routeTask);
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    再给举个栗子: 比如你要应聘阿里巴巴的前端工程师,结果阿里巴巴 HR 告诉你没坑位了,留下你的电话,等有坑位联系你。于是,你美滋滋的留下了联系方式。殊不知,HR 已经留下了好多联系方式。好在 2022 年 2 月 30 号那天,阿里巴巴有了前端工程师的坑位,HR 挨着给留下的联系方式联系了一通。
    案例中阿里巴巴就是目标对象 Subject ,联系方式列表就是用来维护观察者的 observerList ,根据前端职位的有无来调用 notify 方法。

    那什么是发布订阅模式呐?

    基于一个事件(主题)通道,希望接收通知的对象 Subscriber 通过自定义事件订阅主题,被激活事件的对象 Publisher 通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。
    因此发布订阅模式与观察者模式相比,发布订阅模式中有三个角色,发布者 Publisher ,事件调度中心 Event Channel ,订阅者 Subscriber 。
    上面的文字有些难以理解,我们继续以弟子领取任务为栗子,宗门感觉把任务订阅放在任务大殿中有些繁琐,于是决定在任务大殿和弟子中间添加中介。弟子在中介中订阅其需要的任务类型,当任务大殿发布任务后,中介会将发布任务给对应的订阅者。

    宗门任务大殿: 任务发布者 —— Publisher
    中介功能 —— Event Channel

      维护任务类型,以及每种任务下的订阅情况
      给订阅者提供订阅功能 —— == subscribe 功能==
    
    • 1
    • 2

    当宗门发布任务后,中介会给所有的订阅者发布任务 ——== publish 功能==

    在这里插入图片描述
    以目前的热播剧开端为例,临近过年,摸鱼的心思越来越重,每天就迫不及待的等开端更新,想在开端更新的第一刻就开始看剧,那你会怎么做那?总不能时时刻刻刷新页面吧。平台提供了消息订阅功能,如果你选择订阅,平台更新开端后,会第一时间发消息通知你,订阅后,你就可以愉快的追剧了。
    上面案例中,开端就是发布者 Publisher,追剧人就是订阅者 Subscribe,平台则承担了事件通道 Event Channel 功能。

    class PubSub {
        constructor() {
            // 事件中心
            // 存储格式: warTask: [], routeTask: []
            // 每种事件(任务)下存放其订阅者的回调函数
            this.events = {}
        }
        // 订阅方法
        subscribe(type, cb) {
            if (!this.events[type]) {
                this.events[type] = [];
            }
            this.events[type].push(cb);
        }
        // 发布方法
        publish(type, ...args) {
            if (this.events[type]) {
                this.events[type].forEach(cb => cb(...args))
            }
        }
        // 取消订阅方法
        unsubscribe(type, cb) {
            if (this.events[type]) {
                const cbIndex = this.events[type].findIndex(e=> e === cb)
                if (cbIndex != -1) {
                    this.events[type].splice(cbIndex, 1);
                }
            }
            if (this.events[type].length === 0) {
                delete this.events[type];
            }
        }
        unsubscribeAll(type) {
            if (this.events[type]) {
                delete this.events[type];
            }
        }
    }
    
    // 创建一个中介公司
    let pubsub = new PubSub();
    
    // 弟子一订阅战斗任务
    pubsub.subscribe('warTask', function (taskInfo){
        console.log("宗门殿发布战斗任务,任务信息:" + taskInfo);
    })
    // 弟子一订阅战斗任务
    pubsub.subscribe('routeTask', function (taskInfo) {
        console.log("宗门殿发布日常任务,任务信息:" + taskInfo);
    });
    // 弟子三订阅全类型任务
    pubsub.subscribe('allTask', function (taskInfo) {
        console.log("宗门殿发布五星任务,任务信息:" + taskInfo);
    });
    
    // 发布战斗任务
    pubsub.publish('warTask', "猎杀时刻");
    pubsub.publish('allTask', "猎杀时刻");
    
    // 发布日常任务
    pubsub.publish('routeTask', "种树浇水");
    pubsub.publish('allTask', "种树浇水");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
  • 相关阅读:
    【中国知名企业高管团队】系列25:360
    【Java】抽奖系统———保姆学习教程
    【网络安全---XSS漏洞(1)】XSS漏洞原理,产生原因,以及XSS漏洞的分类。附带案例和payload让你快速学习XSS漏洞
    shell_54.linux重定向错误
    大一作业HTML网页作业:中华传统文化题材网页设计5页(纯html+css实现)
    84.(cesium之家)cesium模型在地形上运动
    10.8c++作业
    随想录 Day 71 最小生成树 prim算法 kruskal算法
    基础说明 Reset Vector
    关于网站建设公司哪家好的一些事
  • 原文地址:https://blog.csdn.net/kalvin_y_liu/article/details/127644827