Iterator Pattern 反覆器模式 [Design Pattern in Java]

Posted by Kubeguts on 2020-08-03

將處理陣列或是取值方式的邏輯給封裝起來,不需讓使用者直接處理操作取值的邏輯

沒有Iterator Pattern的情況

假設現在有2種菜單, 分別叫做中式料理菜單 與 西式料理菜單

中式料理菜單是用ArrayList的方式儲存菜單項目

美式料理菜單是用Array[]儲存

中式料理菜單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ChineseMenu {
ArrayList<String> menu;

ChineseMenu() {
this.setMenuItem();
}

public void setMenuItem() {
this.menu = new ArrayList<>();
this.menu.add("麻婆豆腐");
this.menu.add("豬血糕");
}

public ArrayList<String> getMenu() {
return menu;
}
}

美式料理菜單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AmericanMenu {
String[] menu;

AmericanMenu() {
this.menu = new String[3];
this.setMenuItem();
}

public void setMenuItem() {
this.menu[0] = "漢堡";
this.menu[1] = "薯條";
this.menu[2] = "炸雞";
}

public String[] getMenu() {
return menu;
}
}

接著定義一個類別:服務生,但服務生需要知道要怎麼撈內容, ArrayList與Array的撈法就會不一樣,要寫兩個for loop…

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

class Waitress {
ChineseMenu chineseMenu;
AmericanMenu americaMenu;

Waitress(
ChineseMenu chMenu,
AmericanMenu amerMenu
) {
this.chineseMenu = chMenu;
this.americaMenu = amerMenu;
}

// 這時候服務生需要針對各個menu做菜色介紹
public void introduceMenu() {
System.out.println("=======介紹中式料理========");
for(String dish: chineseMenu.getMenu()) {
System.out.println(dish);
}
System.out.println("=======介紹美式式料理========");
for(int i=0; i<americaMenu.getMenu().length; i++) {
System.out.println(americaMenu.menu[i]);
}
}
}

接著呼叫服務生,介紹菜單

1
2
3
4
5
6
7
8
9
public class main {
public static void main(String[] args) {
ChineseMenu chineseMenu = new ChineseMenu();
AmericanMenu americanMenu = new AmericanMenu();
Waitress waitress = new Waitress(chineseMenu, americanMenu);
waitress.introduceMenu();

}
}
1
2
3
4
5
6
7
=======介紹中式料理========
麻婆豆腐
豬血糕
=======介紹美式式料理========
漢堡
薯條
炸雞

問題

可以看到服務生必須要了解如何從菜單取出來,變成每一次若又有新的菜單,那每次服務生又得曉得新菜單的取得方式是怎樣,如此一來就得一直增加for loop, 有N個菜單就有N個for loop, 不大好維護。

Iterator模式登場

該模式主要目的:『讓服務生不需知道其取出細節是如何,只要透過一個標準的介面(Iterator),就可以直接呼叫菜單內容』

定義 Iterator 介面

1
2
3
4
interface Iterator<T> {
Boolean hasNext();
T next();
}

改良一下Menu 將各個Menu實作同一個介面

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
interface Menu<T> {
void setMenuItem();
T getMenu();
}

class ChineseMenu implements Menu<ArrayList<String>> {
ArrayList<String> menu;

ChineseMenu() {
this.setMenuItem();
}

public void setMenuItem() {
this.menu = new ArrayList<>();
this.menu.add("麻婆豆腐");
this.menu.add("豬血糕");
}

public ArrayList<String> getMenu() {
return menu;
}
}

class AmericanMenu implements Menu<String[]> {
String[] menu;

AmericanMenu() {
this.menu = new String[3];
this.setMenuItem();
}

public void setMenuItem() {
this.menu[0] = "漢堡";
this.menu[1] = "薯條";
this.menu[2] = "炸雞";
}

public String[] getMenu() {
return menu;
}
}

這時改用實作兩個Iterator類別 ChineseMenuIteratorAmericanMenuIterator,負責定義如何取出菜單內容

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
43

class ChineseMenuIterator implements Iterator<String> {
ArrayList<String> menu;
int count = -1;

public ChineseMenuIterator(Menu<ArrayList<String>> menu) {
this.menu = menu.getMenu();
}

// ChineseMenu是使用ArrayList定義菜單列表
public Boolean hasNext() {
if (count + 1 >= menu.size()) {
return false;
}
return true;
}

public String next() {
count++;
return this.menu.get(count);
}
}

class AmericanMenuIterator implements Iterator<String> {
String[] menu;
int count = -1;

public AmericanMenuIterator(Menu<String[]> menu) {
this.menu = menu.getMenu();
}

public Boolean hasNext() {
if (count + 1 >= menu.length) {
return false;
}
return true;
}

public String next() {
count++;
return this.menu[count];
}
}

這時回到服務生,若要介紹菜單內容的話,只要使用上面定義的 ChineseMenuIteratorAmericanMenuIterator, 就可以直接取出菜單項目內容了,服務生不需要在曉得要怎麼處理撈取的細節,看到一堆for loop了

Waitress 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
class Waitress {
ChineseMenu chineseMenu;
AmericanMenu americaMenu;

ChineseMenuIterator chineseMenuIterator;
AmericanMenuIterator americanMenuIterator;
Waitress(
ChineseMenu chMenu,
AmericanMenu amerMenu,
ChineseMenuIterator chineseMenuIterator,
AmericanMenuIterator americanMenuIterator
) {
this.chineseMenu = chMenu;
this.americaMenu = amerMenu;
this.chineseMenuIterator = chineseMenuIterator;
this.americanMenuIterator = americanMenuIterator;
}

// 這時候服務生只要透過iterator, 就可以直接用一個for loop全部取出來
public void introduceMenu() {
System.out.println("=======介紹中式料理========");
while(chineseMenuIterator.hasNext()) {
System.out.println(chineseMenuIterator.next());
}
System.out.println("=======介紹美式料理========");
while(americanMenuIterator.hasNext()) {
System.out.println(americanMenuIterator.next());
}
}
}

再度呼叫服務生介紹菜單

Main class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class main {
public static void main(String[] args) {
ChineseMenu chineseMenu = new ChineseMenu();
ChineseMenuIterator chineseMenuIterator = new ChineseMenuIterator(chineseMenu);
AmericanMenu americanMenu = new AmericanMenu();
AmericanMenuIterator americanMenuIterator = new AmericanMenuIterator(americanMenu);
Waitress waitress = new Waitress(
chineseMenu,
americanMenu,
chineseMenuIterator,
americanMenuIterator);
waitress.introduceMenu();
}
}

小結

透過Iterator介面定義如何實作抽取資料結構的內容,讓使用者可以不用費心去了解要怎麼取得,直接呼叫已經有實作Iterator介面的class

備註

Collection 類別中其實就已經定義了Iterator介面,不過為了介紹反覆器模式,故在ArrayList中簡單實作了Iterator介面內容

Reference: https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html