從js的事件模型到觀察者模式到mvc

在開發中有時候須要實現一個對象A的變化而後更新另一個對象B編程

這個實現的最簡單的方式時在目標對象A的方法中添加B更新的邏輯代碼架構

可是咱們但願可以用一種比較優雅的方式實現,好比當需求變化時不須要改A的代碼,而且能夠隨時添加或者刪除處理函數。mvc

在大多數gui編程中,都會提供這個事件機制。異步

在網頁的頁面交互中,咱們能夠註冊本身的方法到輸入框的clikc或者change事件中,async

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

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

//IE中方法使用attachEventui

這樣就能把fn處理方法綁定或者解綁到input的click事件中this

不過,經過這種方式綁定的事件event只能是對象自帶的spa

好比,只能註冊 「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     }

在控制器中實現目標對象到觀察者之間的映射。控制器收到視圖發來的請求,去更新模型。模型變化會自動更新視圖層

相關文章
相關標籤/搜索