反覆器模式(Iterator)

反覆器模式(Iterator)

假如目前有兩家公司(先稱為A、B公司)要合併了

這個合併是要成立為集團企業,但是A、B兩家都還要如以前一樣正常營運

需求是合併了以後,新的集團希望要將兩間公司的員工名單做一個整合

其中,新的集團員工系統中有一個列出員工清冊的功能要被實作出來。

因此新的集團希望能透過此系統就能取出稱為Employee物件

這個功能叫列印集團員工清冊。

 

然而,現在遇到了一些問題:

A公司:目前有3000位員工,目前將整個清冊儲存於ArrayList之中。

而B公司呢?目前有2000位員工,目前是將整個員工名單都儲存陣列中。

但兩個公司都行之有年,

你可以想像一個是將清冊儲存在Excel,而另一家公司是儲存純文字檔般

但是兩家子公司的老闆都不願意去重新建置自己儲存員工的資料結構

誰也不讓步。

那這有可能遇到什麼問題呢?

來看A公司的員工清冊的實踐:

   1: public class A_CompanyEmpList
   2: {
   3:   private ArrayList<Employee> Employees;
   4:   ...
   5:  
   6:   public ArrayList<Employee> getEmployeeList() {
   7:     return Employees;
   8:   }
   9: }

B公司的員工清冊的實踐如下:

   1: public class B_Company_List
   2: {
   3:   private Employee[] Employees;
   4:   ...
   5:   public Employee[] getEmployeeList() {
   6:     return Employees;
   7:   }
   8: }

兩間公司目前都提供了一個getEmployeeList()方法

這個方法會回傳個別公司實踐的員工物件的實踐方法。

因為A回傳了整個員工的Employee ArrayList方法,但是B公司回傳了整個Employee的陣列。

因此我們新的集團員工系統,列印員工清冊的功能的程式碼

可能就是長的像這樣:

   1: public class GroupEmployeeManagementModule
   2: {
   3:   private A_Company_EmployeeList EmployeeListA;
   4:   private B_Company_EmployeeList EmployeeListB;
   5:   ...
   6:   public void printAllEmployeeList() {
   7:   //取得A公司的名單並印出
   8:     ArrayList<Employee> employeeListA= EmployeeListA.getEmployeeList();
   9:     response.write(" 子公司 A 員工清冊");
  10:     for(int i = 0; i <employeeListA.size(); i++) {
  11:       Employee employee= employeeListA.get(i);
  12:       response.write(employee.getName() + " ");
  13:       response.write(employee.getTitle());
  14:      response.write(employee.getSalary());
  15:     }
  16:  
  17:  
  18:   //取得B公司的名單並印出
  19:     Employee[] employeeListB= EmployeeListB.getEmployeeList();
  20:     response.write(" 子公司 B 員工清冊");
  21:     for(int i = 0; i <employeeListB.length ; i++) {
  22:       Employee employee= employeeListB[i];
  23:       response.write(employee.getName() + " ");
  24:       response.write(employee.getTitle());
  25:      response.write(employee.getSalary());
  26:     }
  27:   }
  28:   ...
  29: }

問題出現了,我們新的集團系統”必須”知道這兩個公司在員工清冊上的實踐方式。

因為,我們看到了在取出不同的儲存資料結構後

系統仍要知道如何操作回傳的原始資料結構

也就是說這個系統要知道回傳的是陣列或是ArrayList

 

或是萬一….未來要是新增了List結構,或是HashTable實踐的員工清冊物件

我們的集團系統就必須修改”printAllEmployeeList”方法,加入不同資料結構的程式碼

這意謂著,我們的集團員工管理的類別與各子公司的類別緊緊的綑綁在一起,集團員工管理的員別並未對修改關閉。

!!

進入我們反覆器模式的主題了,首先我們要思考的是

如何將集團與子公司的類別來鬆綁呢?

既然不能知道底層實踐的方法,那我們就需要一個更為一般化的方法來取得員工的物件。

封裝會變動的部分,不同公司之間會變動的部分在於資料結構,因此造成巡訪各節點的方式不同。

我們先透過一個介面將之封裝 起來:Iterator 反覆器:

這個反覆器需要具備一般化的動作,在迴圈巡訪的機制下,我們需要的是

1.取得下一個元素的方法

2.判斷是否還有下一個元素

因此我們設計如下:

   1: public interface I_Iterator
   2: {
   3:     public Object next();
   4:     public boolean hasNext();
   5: }
   6:  
   7: .....
   8:  
   9: while(iterator.hasNext()){
  10:     Employee employee = iterator.next();
  11: }

這樣一來,使用Iterator的集團系統,只需要知道如何操作Iterator,而不需要知道A、B公司背後操作員工清冊的知識。

上述程式提供了兩個方法:next()是傳回下一個元素,而hasNext()是判斷是否還有下一個元素。

如此一來,我們就可以透過迴圈的方式來走遍資料結構

也因此,AB公司自然就有責任,去實踐Iterator,因為只有他們知道怎麼走訪正確的資料結構:

   1: public class A_Company_EmpListIterator : I_Iterator
   2: {
   3:   private ArrayList<Employee> employees;
   4:   int position = 0;
   5:  
   6:   public A_Company_EmpListIterator (ArrayList<Employee>  employees) {
   7:     this.employees= employees;
   8:   }
   9:  
  10:   public Object next() {
  11:     Employee employee= employees[position]
  12:     position++;
  13:     return employee;
  14:   }
  15:  
  16:   public hasNext() {
  17:     return (position < employees.size()) && (employees[position] != null);
  18:   }
  19: }

而B公司也是一樣的道理,實作I_Iterator並提供B_Company_EmpListIterator

 

未來我們甚至可以整合我們的Iterator,我們定義一個GroupEmpList介面:

提供建置Iterator的方法:

   1: public interface GroupEmpList
   2: {
   3:     public I_Iterator createIterator();
   4: }

 

而只要A_Company_EmployeeList 與B_Company_EmployeeList 都實踐這個介面,提供自己的Iterator :

   1: public class A_Company_EmployeeList : GroupEmpList
   2: {
   3:   private ArrayList<Employee> employees;
   4:   ...
   5:   public Iterator createIterator() {
   6:     return new A_Company_EmpListIterator(employees);
   7:   }
   8: }
   9:  
  10: public class B_Company_EmployeeList :GroupEmpList
  11: {
  12:   private Employee[] employees;
  13:   ...
  14:   public Iterator createIterator() {
  15:     return new B_Company_EmpListIterator(employees);
  16:   }
  17: }

 

現在,集團只要針對GroupEmpList以及Iterator兩個抽象介面來做事就可以了。不再依賴實體子公司的類別。

 

未來你可以聯想到還可以加入Add、Remove的功能,只要在個別的具象類別中去實作自己所熟知的資料結構型態即可囉。

 

寫過.NET的話,我們可能很熟悉反覆器模式其實已經在我們的環境中處處可見。很多時候我們並不需要去實作Iterator的類別。例如List、Array、已經實做了IEnumerable Interface(介面列舉 ),用起來理應十分順手了。

image

 

回歸理論:以下定義與守則皆來自於深入淺出設計模式(HeadFirst DesignPattern)

反覆器模式Iterator:

讓我們能夠取得一個聚集內的每一個元素,而不需要此聚集將其實踐方式暴露出來

這個模式提供了一個方式可以逐步走過一個聚集(Aggregation)中的每個元素,而不同暴露這個聚集的底層實踐的方式。看到上類別圖,我們是為了將客戶與聚集鬆綁!

 

這樣的精神看起來有點像是FactoryMethod Pattern(工廠方法模式),都是讓次類別去決定要提供的物件型別。

 

單一責任守則

我們的子公司員工清冊的類別提供 Iterator 後,就可以專心處理自己的工作(管理員工項目),而不需要應付走訪元素的請求,只要把它們轉給 反覆器來負責就好。

一個類別只應承擔一項責任。只要責任改變,類別就必須修改。假設它承擔了兩項責任,那麼它必須修改的機率就大增

設計守則

一個類別應該只具有一個改變的理由單一責任守則

一個遵循單一責任守則的類別,容易具有較高的內聚力(cohesion)。但事實上責任的切分是很困難的議題

封裝會變動的部分

在這裡會變動的部分是聚集底層資料結構與其巡訪方式,我們以 Iterator 介面將它封裝起來。

 

 

上述程式碼所實踐的也稱為 external iterator(外部反覆器)

因此一般還有另一個稱為internal iterator(內部反覆器)

Iterator會自動在聚集間走訪,而客戶告訴Iterator要做麼事就好了。

 

C#的內部反覆器實例

   1: List<string> stringList;
   2: stringList.add("A");
   3: stringList.add("B");
   4:  
   5: foreach (string S in stringList)
   6: {
   7:   //do something with S
   8: }

很顯而易見吧