从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 }
结果
该模式可实现独立的添加观察者,而无需修改目标对象和其他观察者对象。目标和观察者之间是通过抽象耦合的,观察者只需要实现指定的接口,便可得到目标对象的更新。
可改进的说明
- 目标发送通知时,并不知道有多少个对象需要更新,也不知道每个观察者的更新的具体实现,所以有些接口会耗时很长或者出现异常情况,这就需要我们在代码中实现异步和异常捕获操作。
- 目标发送通知时,可以通过把发生改变的详细信息通过参数方式传递给观察者,这称为推模型,但是不一定所有的观察者都需要这些的信息 。另外一种是目标对象仅发送最小信息发出,再由观察者去请求需要的信息,这叫拉模型。
- 观察多个对象跟观察感兴趣的改变,需要我们更改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 }
在控制器中实现目标对象到观察者之间的映射。控制器收到视图发来的请求,去更新模型。模型变化会自动更新视图层