Design Pattern_Skill_3_觀察者模式(Observer Pattern)_電子郵件註冊及取消訂閱通知_老師課堂聯繫方式通知





新的學期一開始
一般第一堂學期初的課程
老師們都會做課程簡介跟公布自己的聯繫方式
讓同學有問題可以做溝通聯繫

此時同學們開始做筆記紀錄好老師的聯繫方式
但老師學期間某一天
突然聯繫方式又更改了!!!!!
可能有教職員系統更新....
可能剛好老師換手機....
諸如此類
一旦通知沒有到位
則可能導致學生無法聯繫到老師

我們怎麼實踐
老師電話號碼一更改所有學生的紀錄也都全自動更新呢?


這時類似的關係
我們會有一個學生及一個老師的類別

學生主要是用來描述某某....紀錄到的老師電話
老師則有電話號碼的資訊




Student Class
 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
public class Student {
 private String _name="";
 private String _phone = "";
 
 public Student(String strName) {
  this._name = strName;
 }
 
 public String getName(){
  return this._name;
 }
 
 public void setName(String strName) {
  this._name = strName;
 }

 public String getPhone() {
  return _phone;
 }

 public void setPhone(String strPhone) {
  this._phone = strPhone;
 }
 
 public void show() {
  System.out.println("Name:"+getName()+"\nTeacher's phone number:"+getPhone());
 }
 
 
}



Teacher Class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Teacher {
 private String _phone;

 public Teacher(String strPhone) {
  this._phone = strPhone;
 }
 
 public String getPhone() {
  return _phone;
 }

 public void setPhone(String strPhone) {
  this._phone = strPhone;
 }
 
}


Main

 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
public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  System.out.println("開學第一堂課 老師課程介紹跟聯繫方式課堂告知");
  Teacher Wang = new Teacher("0912345678");
  Student Amy = new Student("Amy");
  Student Jason = new Student("Jason");
  Student Mike = new Student("Mike");
  
  Amy.setPhone(Wang.getPhone());
  Jason.setPhone(Wang.getPhone());
  Mike.setPhone(Wang.getPhone());
  
  Amy.show();
  Jason.show();
  Mike.show();
  
  System.out.println("學期間突然有聯繫方式變更");
  Wang.setPhone("0987654321");
  
  Amy.setPhone(Wang.getPhone());
  Jason.setPhone(Wang.getPhone());
  Mike.setPhone(Wang.getPhone());
  
  Amy.show();
  Jason.show();
  Mike.show();
 }

}




Main主程式中互動模擬



一開始想到的設計方案是直接於學生類別中去定義存取老師電話號碼的欄位
那也讓人感覺用戶端的程式碼有點冗長且重複性也高





這裡我們進行第二階段改寫
在學生和老師之間加入一個單向關聯
老師的物件實體直接定義在學生類別中
這樣可以省去每次都要針對不同學生實體去設定新電話號碼的動作
於一開始實體化傳入至建構子中就有包含老師物件實體間接可以呼叫到該更新後的號碼

Teacher class 保持不變

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Teacher {
 private String _phone;

 public Teacher(String strPhone) {
  this._phone = strPhone;
 }
 
 public String getPhone() {
  return _phone;
 }

 public void setPhone(String strPhone) {
  this._phone = strPhone;
 }
 
}



稍稍調整一下Student class

Student
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student {
 private String _name="";
 
 public String getName(){
  return this._name;
 }
 
 public void setName(String strName) {
  this._name = strName;
 }
 
 private Teacher _teacher;
 
 public Student(String strName, Teacher t) {
  this._name = strName;
  this._teacher = t;
 }

 public void show() {
  System.out.println("Name:"+getName()+"\nTeacher's phone number:"+_teacher.getPhone());
 }

}


Main 程式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  System.out.println("開學第一堂課 老師課程介紹跟聯繫方式課堂告知");
  Teacher Wang = new Teacher("0912345678");
  Student Amy = new Student("Amy",Wang);
  Student Jason = new Student("Jason",Wang);
  Student Mike = new Student("Mike",Wang);
  
  Amy.show();
  Jason.show();
  Mike.show();
  
  System.out.println("學期間突然有聯繫方式變更");
  Wang.setPhone("0987654321");

  Amy.show();
  Jason.show();
  Mike.show();
 }

}


雖然效果依舊而且確實省去一點程式但依舊有點問題......
學生類別中直接存入老師物件
導致緊密耦合了
當你到下學期或是畢業沒有再上那個老師的課程時
你依舊存有該老師的資料
這樣子會導致物件和物件底層發生直接密集的關聯!!!
而不夠靈活
因此我們需要使用到觀察者模式
來解決此類問題


觀察者模式(Observer Pattern)/發佈訂閱模式
當某物件發生異變時則須將其改變廣播給其他相依物件並做相應調整。
可確保互動關係不會直接關聯(物件之間關係的解耦)

存在兩種角色:
觀察者和被觀察者
兩者之間存在觀察的邏輯關聯
當被觀察者發生改變時,觀察者就會察覺到變化並做相應的行為。
觀察並非直接調用
實踐觀察者模式有很多形式,較為直觀的一種是使用
「註冊----->通知----->撤銷註冊」 的形式


再觀察者模式中惠存一個被觀察者(有點像間諜)
還會有一些觀察者看著他
這些觀察者若想獲得被觀察者的異動則需進行註冊成為被觀察者的觀察者列表一員



解決方案:
Step1.觀察者將自己註冊到被觀察者物件當中,被觀察者物件再將其存放到一個容器(比方佇列、List、Array、ArrayList...etc)

這裡一張示意圖
中間有個可疑墨鏡男有點像間諜的就是被觀察者
一些觀察者藉由註冊手續成為觀察者列表中一員

那也可以想成就是生活中的郵件或影片訂閱
一有更新則有及時發佈訊息通知
當然也可以取消訂閱


Step2.被觀察者物件發生某些變化後,從容器中獲取所有
註冊的觀察者,將異變通知給觀察者。


Step3.撤銷註冊
觀察者告訴被觀察者要撤銷觀察,被觀察者從容器中把觀察者去除。





如我們用觀察者模式的類別關聯圖
套用剛剛的師生關係



那麼學生就是觀察者
而老師則是被觀察者其內部會存在一個集合容器
可往內添加(訂閱、註冊)觀察者物件
並在狀態有更改時立即通知到觀察者

此為兩者底層具體關聯
不過我們希望兩者不要耦合度太高
因此我們各自在現有兩者往上抽取出來抽象介面

觀察者(抽象)介面:若想成為觀察者則必須實作update該方法,目的就是在接收到被觀察者送出的更新通知後做相應更新。

觀察者和被觀察者不一定是多對一的關係喔!!!!
學生可能不只觀察老師也可能觀察學校、其他老師、同學等等
相反過來老師也不一定只會被學生觀察
也可能有同事或其他人!!

此時透過抽離出來的抽象介面實作抽象方法即可變成各自對應腳色


被觀察者(抽象)介面:握想成為被觀察者則必須實作三方法
1.要能往自己內部觀察者列表添加觀察者
2.可註銷觀察者(從觀察者列表移除觀察者實體)
3.可發出更新通知給觀察者





被觀察者(Teacher)


 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
package Pattern.Observer;

import java.util.ArrayList;

public class Teacher implements Subject{
 private String _phone;
 private ArrayList obsList;
 
 public Teacher() {
  obsList = new ArrayList();
 }
 
 public String getPhone() {
  return _phone;
 }
 
 public void setPhone(String strPhone) {
  this._phone = strPhone;
  notifyObserver();//當老師類別一呼叫set更新電話號碼後立即更新觀察者列表所有成員
 }

 @Override
 public void registerObserver(Object o) {
  // TODO Auto-generated method stub
  this.obsList.add(o);
 }

 @Override
 public void removeObserver(Object o) {
  // TODO Auto-generated method stub
  this.obsList.remove(o);
 }

 @Override
 public void notifyObserver() {
  // TODO Auto-generated method stub
  for(int i=0;i<this.obsList.size();i++) {
   ((Observer) this.obsList.get(i)).update(getPhone());
  }
 }
 
}



觀察者(Student)

 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
package Pattern.Observer;

public class Student implements Observer {
 private String _name="";
 private String _phone = "";
 public String getName(){
  return this._name;
 }
 
 public void setName(String strName) {
  this._name = strName;
 }
 
 public String getPhone() {
  return _phone;
 }

 public void setPhone(String strPhone) {
  this._phone = strPhone;
 }
 
 public Student(String strName) {
  this._name = strName;
 }


 @Override
 public void update(Object o) {
  // TODO Auto-generated method stub
  this.setPhone((String) o);
 }
 
 public void show() {
  System.out.println("Name:"+getName()+"\nTeacher's phone number:"+getPhone());
 }

}


Main


 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
package Pattern.Observer;

public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  System.out.println("開學第一堂課 老師課程介紹跟聯繫方式課堂告知");
  Teacher Wang = new Teacher();
  Student Amy = new Student("Amy");
  Student Jason = new Student("Jason");
  Student Mike = new Student("Mike");
  
  //Step1.註冊觀察者至被觀察者當中的觀察者列表
  Wang.registerObserver(Amy);
  Wang.registerObserver(Jason);
  Wang.registerObserver(Mike);
  
  Wang.setPhone("0912345678");
  Amy.show();
  Jason.show();
  Mike.show();
  
  System.out.println("第一次學期間聯繫方式變更");
  Wang.setPhone("0987654321");
  Wang.removeObserver(Mike);
  
  Amy.show();
  Jason.show();
  Mike.show();
  
  System.out.println("第二次學期間聯繫方式變更");
  Wang.setPhone("0955555555");
  Amy.show();
  Jason.show();
  Mike.show();//不再更新
 }

}











Reference link:
Observer Pattern Files
http://www.forestandthetrees.com/2008/10/09/observer-pattern-files/

The Observer Pattern in Java
https://dzone.com/articles/observer-pattern-java


https://dotblogs.com.tw/joysdw12/2013/03/13/96531
https://ithelp.ithome.com.tw/articles/10204117
https://xyz.cinc.biz/2013/06/observer-pattern.html

留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題