从js的事件模型到观察者模式到mvc

在开发中有时候需要实现一个对象A的变化然后更新另外一个对象B

这个实现的最简单的方式时在目标对象A的方法中添加B更新的逻辑代码

但是我们希望能够用一种比较优雅的方式实现,比如当需求变化时不需要改A的代码,并且可以随时添加或者删除处理函数。

在大多数gui编程中,都会提供这个事件机制。

在网页的页面交互中,我们可以注册自己的方法到输入框的clikc或者change事件中,

document.getElementByTag("input").addEventListener("click",fn);

document.getElementByTag("input").removeEventListener("click",fn);

//IE中方法使用attachEvent

这样就能把fn处理方法绑定或者解绑到input的click事件中

不过,通过这种方式绑定的事件event只能是对象自带的

比如,只能注册 “click”,"focus","blur","change","load","mouseout"等一些对象元素自带的事件

如果我们想给input添加一个自定义事件或者给自定义的panel添加事件处理程序的话,只能通过自己实现了。

下面来个实现

 1             //输入框变化,改变其他对象
 2             //事件插件,其他对象可扩展此对象进行事件监控
 3             var Events = (function () {
 4                 var events = {};
 5                 return {
 6                     addListeners: function (fn, callback) {
 7                         events[fn] = events[fn] || [];
 8                         events[fn].push(callback);
 9                     },
10                     removeListeners: function (fn, callback) {
11                         events[fn] = events[fn].filter(item => {
12                             return item != callback;
13                         })
14                     },
15                     fireEvent: function (fn, param) {
16                         var args = Array.prototype.slice.call(arguments, 0),
17                             me = this;
18                         if (events[fn]) {
19                             events[fn].forEach(item => {
20                                 item.call(this, param);
21                             })
22                         }
23                     }
24                 }
25             })();
26 
27             //输入组件,和他的两个实例方法
28             function Input() { }
29             Input.prototype.userInput = function (param) {
30                 this.fireEvent("change", param);
31             }
32             Input.prototype.userLeave = function (param) {
33                 this.fireEvent("keyup", param);
34             }
35 
36             //监控输入组件,根据输入框变化更新此面板
37             function Panel() { }
38             Panel.prototype.showClick = function (param) {
39                 console.log("Panel show");
40             }
41             Panel.prototype.hideClick = function (param) {
42                 console.log("Panel hide");
43             }
44         
45         //实例化input对象,并继承Events的属性和方法
46             var input = new Input();
47             Object.assign(input, Events);
48 
49             var panel = new Panel();
50 
51         //注册处理程序到change和keyup事件
52             input.addListeners("change", panel.showClick);
53             input.addListeners("keyup", panel.hideClick);
54 
55             //通过实例方法触发change和keyup事件
56             input.userInput();  //输出结果:--Panel show
57             input.userLeave();  //输出结果:--Panel hide

上面Events对象 是个简易的事件绑定方法,所有其他的对象都可以扩展此方法来实现事件的绑定。

C#用委托来实现事件绑定

 1 using System;
 2 
 3 namespace ConsoleApp2
 4 {
 5 
 6     public class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             var subject = new Subject();
11             var observer = new Observer(subject);
12             subject.update();
13             Console.ReadLine();
14 
15         }
16     }
17 
18     public delegate void ChangeHandler();
19 
20     public class Subject
21     {
22 
23         public ChangeHandler changeHandler;
24         public void update()
25         {
26             //if (changeHandler != null)
27             //{
28             //    changeHandler();
29             //}
30             changeHandler?.Invoke();
31 
32         }
33     }
34 
35     public class Observer
36     {
37         public Observer(Subject subject)
38         {
39             subject.changeHandler += new ChangeHandler(change);
40         }
41         public void change()
42         {
43             Console.WriteLine("subject had updated");
44         }
45     }
46 }

通过一个对象改变从而引起其他对象变化的情况,在gof书籍中称为观察者模式,或者叫发布-订阅模式  ,属于对象行为型模式

意图

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并被自动更新

适用性

适用于以下的任一种情况

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这二者封装在独立的对象中以使它们可以各自独立的改变和复用
  • 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变
  • 当一个对象必须通知其他对象,而他又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的。

下面来实现这个模型

我们把这个状态变化的对象成为目标对象(Subject),根据目标对象变化而变化的对象成为观察者(Observers)

目标对象

 1 //目标对象基类    
 2 public abstract class BasSubject
 3     {
 4         //观察者对象
 5         public List<BasObserver> observerList = new List<BasObserver>();
 6         //状态
 7         public string State
 8         {
 9             get; set;
10         }
11         /// <summary>
12         /// 附加观察者
13         /// </summary>
14         /// <param name="observer"></param>
15         public void attachObserver(BasObserver observer)
16         {
17             observerList.Add(observer);
18         }
19         /// <summary>
20         /// 移除观察者 
21         /// </summary>
22         /// <param name="observer"></param>
23         public void detachObserver(BasObserver observer)
24         {
25             observerList.Remove(observer);
26         }
27         /// <summary>
28         /// 状态变更
29         /// </summary>
30         public virtual  void onChangeState()
31         {
32             observerList.ForEach(async item =>
33             {
34                 await item.update();
35             });
36 
37         }
38 
39     }
40 // 子类
41    public class Subject:BasSubject
42     {
43         public void onChangeState(string state)
44         {
45             State = state;
46             base.onChangeState();
47         }
48     }

观察者对象 

 1 //观察者抽象类或者接口
 2   public abstract class BasObserver
 3     {
 4         public BasSubject subject;
 5         public BasObserver() {
 6 
 7         }
 8         public BasObserver(BasSubject subject) {
 9             this.subject = subject;
10             this.subject.attachObserver(this);
11         }
12         public virtual async Task update() {
13         }
14     }
15 //对象A
16     public class ObserverA : BasObserver
17     {
18         BasSubject subject;
19         public ObserverA()
20         {
21 
22         }
23         public ObserverA(BasSubject subject) : base(subject)
24         {
25             this.subject = subject;
26         }
27         public override async Task update()
28         {
29             if (this.subject.State == "interest")
30             {
31                 await Task.Run(() =>
32                 {
33                     Thread.Sleep(1000);
34                 });
35                 Console.WriteLine("interest change---from{0}", this.GetType());
36             }
37 
38         }
39     }
40 //对象B
41     public class ObserverA : BasObserver
42     {
43         BasSubject subject;
44         public ObserverA()
45         {
46 
47         }
48         public ObserverA(BasSubject subject) : base(subject)
49         {
50             this.subject = subject;
51         }
52         public override async Task update()
53         {
54             if (this.subject.State == "interest")
55             {
56                 await Task.Run(() =>
57                 {
58                     Thread.Sleep(1000);
59                 });
60                 Console.WriteLine("interest change---from{0}", this.GetType());
61             }
62 
63         }
64     }

执行

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Subject subject = new Subject();
 6             ObserverA observerA1 = new ObserverA(subject);
 7             ObserverA observerA2 = new ObserverA();
 8             ObserverB observerB = new ObserverB(subject);
 9 
10             subject.onChangeState("interest");
11             Console.WriteLine();
12             subject.onChangeState("un interest");
13             Console.ReadLine();
14         }
15     }

结果

该模式可实现独立的添加观察者,而无需修改目标对象和其他观察者对象。目标和观察者之间是通过抽象耦合的,观察者只需要实现指定的接口,便可得到目标对象的更新。

可改进的说明

  1. 目标发送通知时,并不知道有多少个对象需要更新,也不知道每个观察者的更新的具体实现,所以有些接口会耗时很长或者出现异常情况,这就需要我们在代码中实现异步和异常捕获操作。
  2. 目标发送通知时,可以通过把发生改变的详细信息通过参数方式传递给观察者,这称为推模型,但是不一定所有的观察者都需要这些的信息 。另外一种是目标对象仅发送最小信息发出,再由观察者去请求需要的信息,这叫拉模型。
  3. 观察多个对象跟观察感兴趣的改变,需要我们更改update接口接受目标实例和目标状态,用来判断是哪个目标发来的更新,和判断是不是感兴趣的更新。


应用场景

观察者模式最早应用于malltalk的mvc架构中。

其中model为目标对象,view为观察者,一个model可能会绑定到多个视图中,当model变化时需要同时改变各个view。

在项目开发中,我们提倡职责分离,所以我们可以添加一个controller层来处理响应用户输入,更新模型等功能

针对上面的例子加入控制器的代码如下

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var observer = new Observer();
 6             observer.submit();
 7             Console.ReadLine();
 8         }
 9     }
10 
11     public class Controller {
12         Subject subject;
13         Observer observer;
14         /// <summary>
15         /// 注册观察者到目标
16         /// </summary>
17         /// <param name="observer"></param>
18         public Controller(Observer observer) {
19             this.observer = observer;
20             this.subject = new Subject();
21             subject.attachObserver(observer); 
22         }
23         /// <summary>
24         /// 处理请求 
25         /// </summary>
26         public void doSomeThing () {
27             subject.State = "interest";
28             subject.onChangeState();
29         }
30 
31     }
32     public class Subject {
33         List<Observer> list;
34         public Subject() {
35             list = new List<Observer>();
36         }
37         public string State { get; set; }
38         public void attachObserver(Observer v)
39         {
40             list.Add(v);
41         }
42         public void onChangeState() {
43             list.ForEach(item =>
44             {
45                 item.update(this.State);
46             });
47         }
48     }
49     public class Observer {
50         Controller c;
51         public Observer() {
52             c = new Controller(this);
53         }
54         /// <summary>
55         /// 用户操作
56         /// </summary>
57         public void submit() {
58             c.doSomeThing();
59         }
60         /// <summary>
61         /// 刷新
62         /// </summary>
63         /// <param name="label"></param>
64         public void update(string label) {
65             Console.WriteLine(label);
66         }     
67 
68     }

在控制器中实现目标对象到观察者之间的映射。控制器收到视图发来的请求,去更新模型。模型变化会自动更新视图层

 

posted @ 2017-09-14 02:02  btohad  阅读(355)  评论(0编辑  收藏  举报