《Head First設計模式》第九章(1)迭代器模式

迭代器模式

因為這一章涉及到兩個模式,內容有點多,還有一個組合模式留到下一篇寫吧。

有許多種方法可以把對象堆起來成為一個集合(collection)。你可以把它們放進數組、堆棧、列表或者是散列表(Hashtable)中,這是你的自由。每一種都有它自己的優點和適合的使用時機,但總有一個時候,你的客戶想要遍歷這些對象,而當他這么做時,你打算讓客戶看到你的實現嗎?我們當然希望最好不要!這太不專業了。本章的迭代器模式將能讓客戶遍歷你的對象而又無法窺視你存儲對象的方式

先來看看迭代器模式的定義

  • 提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內部的表示。

題例:有兩家餐廳,披薩店和煎餅店,它們合并了,雖然可以在一個地方同時想用煎餅屋的早餐和餐廳的午餐,但是煎餅屋的菜單用用的ArrayList記錄菜單的,而餐廳用的是數組,而兩家餐館都不愿意修改自己的實現。畢竟有很多代碼依賴它們。

幸好兩家都統一實現了MenuItem:

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

44

45

46

47

//菜單項,保存了菜單信息

public class MenuItem {

????private String name;

????private String description;

????private boolean vegetarian;

????private double price;

?

????public MenuItem(String name, String description,?boolean vegetarian,?double price) {

????????super();

????????this.name = name;

????????this.description = description;

????????this.vegetarian = vegetarian;

????????this.price = price;

????}

?

????public String getName() {

????????return name;

????}

?

????public void setName(String name) {

????????this.name = name;

????}

?

????public String getDescription() {

????????return description;

????}

?

????public void setDescription(String description) {

????????this.description = description;

????}

?

????public boolean isVegetarian() {

????????return vegetarian;

????}

?

????public void setVegetarian(boolean vegetarian) {

????????this.vegetarian = vegetarian;

????}

?

????public double getPrice() {

????????return price;

????}

?

????public void setPrice(double price) {

????????this.price = price;

????}

}

再來看看兩家店各自的菜單實現:

煎餅店:用ArrayList

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// 煎餅餐店對象,用ArrayList保存了菜單。

public class PancakeHouseMenu {

????ArrayList<MenuItem> menuItems;

?

????public PancakeHouseMenu() {

????????menuItems =?new ArrayList<MenuItem>();

????????addItem("煎餅1號",?"牛肉煎餅",?false,?2.99);

????????addItem("煎餅2號",?"素食煎餅",?true,?1.49);

????}

?

????public void addItem(String name, String description,?boolean vegetarian,?double price) {

????????MenuItem menu =?new MenuItem(name, description, vegetarian, price);

????????menuItems.add(menu);

????}

?

????public ArrayList<MenuItem> getMenuItems() {

????????return menuItems;

????}

}

披薩店:用數組

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// 披薩餐廳對象,用數組保存了菜單信息。

public class PizzaHouseMenu {

????static final int MAX_ITEMS =?2;

????int numberOfItems =?0;

????MenuItem[] menuItems;

?

????public PizzaHouseMenu() {

????????menuItems =?new MenuItem[MAX_ITEMS];

????}

?

????public void addItem(String name, String description,?boolean vegetarian,?double price) {

????????MenuItem menu =?new MenuItem(name, description, vegetarian, price);

????????if (numberOfItems >= MAX_ITEMS)

????????????System.out.println("對不起,菜單數量已滿");

????????else

????????????menuItems[numberOfItems++] = menu;

????}

?

????public MenuItem[] getMenuItems() {

????????return menuItems;

????}

}

有兩種不同的菜單表現方式,這會帶來什么問題?

假設你是一個女招待下面是你做的事,你會怎么辦?

  1. printMenu(); 打印出菜單上的每一項
  2. printBreakfastMenu(); 只打印早餐
  3. printLunchMenu(); 只打印午餐
  4. printVegetarianMenu(); 打印所有的素食菜單
  5. isItemVegetarian(name); 查詢指定的菜品是否是素食

指定項的名稱,如果該項是素食的話,返回true,否則返回false
打印沒分菜單上的所有項,必須調用PancakeHouseMenu和PizzaHouseMenu的getMenuItenm()方法,來取得它們各自的菜單項,兩者返回類型是不一樣的。

1

2

3

4

5

PancakeHouseMenu pancakeHouseMenu=new PancakeHouseMenu();

ArrayList breakfastItems=pancakeHouseMenu.getMenuItems();

?

PizzaHouseMenu pizzaHouseMenu=new PizzaHouseMenu();

MenuIten[] linchItenms=pizzaHouseMenu.getMenuItens();

打印菜單需要的數組和集合,用循環將數據一一列出來

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

public class Client {

????public static void main(String[] args) {

????????// 先獲得煎餅餐廳的菜單集合

????????PancakeHouseMenu pancakeHouseMenu =?new PancakeHouseMenu();

????????ArrayList<MenuItem> menusOfPancake = pancakeHouseMenu.getMenuItems();

?

????????// 在獲得披薩餐廳的菜單數組

????????PizzaHouseMenu pizzaHouseMenu =?new PizzaHouseMenu();

????????MenuItem[] menusOfPizza = pizzaHouseMenu.getMenuItems();

?

????????//我們用循環將數據一一列出來

????????for (int i =?0; i < menusOfPancake.size(); i++) {

????????????MenuItem menu = menusOfPancake.get(i);

????????????System.out.print(menu.getName() +?",價格:");

????????????System.out.print(menu.getPrice() +?",");

????????????System.out.print(menu.getDescription() +?"\n");

????????}

????????System.out.println();

????????for (int i =?0; i < menusOfPizza.length; i++) {

????????????MenuItem menu = menusOfPizza[i];

????????????System.out.print(menu.getName() +?",價格:");

????????????System.out.print(menu.getPrice() +?",");

????????????System.out.print(menu.getDescription() +?"\n");

????????}

????}

}

我們總是需要處理這兩個菜單的遍歷,如果還有第三家餐廳以不同的方式實現菜單集合,我們就需要有第三個循環。

可以封裝遍歷嗎?

? 可以封裝變化的部分。很明顯,這里發生的變化是:由不同的集合類型所造成的遍歷。但是,這能夠被封裝嗎?讓我們來看看這個想法……

  • 要便利煎餅餐廳,我們需要使用ArrayList的size()和get()方法;

  • 要便利披薩餐廳,我們需要使用數組的length字段和在中括號中輸入索引;

  • 現在我們創建一個對象,將它稱為迭代器(Iterator),利用它來封裝“遍歷集合內的每個對象的過程”;

    想要在餐廳菜單中加入一個迭代器,我們需要先定義迭代器接口,然后為披薩餐廳創建一個迭代器類:

1

2

3

4

public interface Iterator {

????boolean hasNext();

????Object next();

}

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

44

45

public class PizzaIterator?implements Iterator {

?

????MenuItem[] items;

????int position =?0;

?

????public PizzaIterator(MenuItem[] items) {

????????this.items = items;

????}

?

????// 判斷數組下一個索引是否還有元素

????public boolean hasNext() {

????????if(position >= items.length || items[position] ==?null)

????????????return false;

????????else return true;

????}

?

????// 獲得當前索引位置的元素

????public Object next() {

????????MenuItem item = items[position++];

????????return item;

????}

}

?

public class PancakeIterator?implements Iterator {

?

????ArrayList<MenuItem> items;

????int position =?0;

?

????public PancakeIterator(ArrayList<MenuItem> items) {

????????this.items = items;

????}

?

????// 判斷數組下一個索引是否還有元素

????public boolean hasNext() {

????????if(position >= items.size() || items.get(position) ==?null)

????????????return false;

????????else return true;

????}

?

????// 獲得當前索引位置的元素

????public Object next() {

????????MenuItem item = items.get(position);

????????return item;

????}

}

創建好迭代器后,改寫披薩餐廳的代碼,創建一個PizzaMenuIterator,并返回給客戶:

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

public class PizzaHouseMenu {

????static final int MAX_ITEMS =?2;

????int numberOfItems =?0;

????MenuItem[] menuItems;

?

????public PizzaHouseMenu() {

????????menuItems =?new MenuItem[MAX_ITEMS];

????????addItem("披薩1號",?"素食披薩",?true,?4.99);

????????addItem("披薩2號",?"海鮮蛤蜊披薩",?true,?5.99);

????}

?

????public void addItem(String name, String description,?boolean vegetarian,?double price) {

????????MenuItem menu =?new MenuItem(name, description, vegetarian, price);

????????if (numberOfItems >= MAX_ITEMS)

????????????System.out.println("對不起,菜單數量已滿");

????????else

????????????menuItems[numberOfItems++] = menu;

????}

?

????public Iterator createIterator() {

????????return new PizzaIterator(menuItems);

????}

}

?

public class PancakeHouseMenu {

????ArrayList<MenuItem> menuItems;

?

????public PancakeHouseMenu() {

????????menuItems =?new ArrayList<MenuItem>();

????????addItem("煎餅1號",?"牛肉煎餅",?false,?2.99);

????????addItem("煎餅2號",?"素食煎餅",?true,?1.49);

????}

?

????public void addItem(String name, String description,?boolean vegetarian,?double price) {

????????MenuItem menu =?new MenuItem(name, description, vegetarian, price);

????????menuItems.add(menu);

????}

?

????public Iterator createIterator() {

????????return new PancakeIterator(menuItems);

????}

}

我們不再需要getMenuItems()方法,而是用createIterator()方法代替,用來從菜單項數組創建一個迭代器,并把他返回給客戶,返回迭代器接口。客戶不需要知道餐廳菜單使如何實現維護的,也不需要知道迭代器是如何實現的。客戶只需直接使用這個迭代器遍歷菜單即可。下面修改一下客戶類的調用:

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

public class Waitress {

????PancakeHouseMenu pancake;

????PizzaHouseMenu pizza;

?

????public Waitress(PancakeHouseMenu pancake, PizzaHouseMenu pizza) {

????????this.pancake = pancake;

????????this.pizza = pizza;

????}

?

????public void printMenu() {

????????Iterator pizzaIterator = pizza.createIterator();

????????printMenu(pizzaIterator);

?

????????Iterator pancakeIterator = pancake.createIterator();

????????printMenu(pancakeIterator);

????}

?

????private void printMenu(Iterator iterator) {

????????while(iterator.hasNext()) {

????????????MenuItem menu = (MenuItem)iterator.next();

????????????System.out.print(menu.getName() +?",價格:");

????????????System.out.print(menu.getPrice() +?",");

????????????System.out.print(menu.getDescription() +?"\n");

????????}

????}

}

1

2

3

4

5

6

7

8

9

public class Client {

????public static void main(String[] args) {

????????PancakeHouseMenu pancake =?new PancakeHouseMenu();

????????PizzaHouseMenu pizza =?new PizzaHouseMenu();

?

????????Waitress waitress =?new Waitress(pancake, pizza);

????????waitress.printMenu();

????}

}

輸出結果:

圖片說明

到目前為止,我們將客戶調用與餐廳的菜單數據接口解耦了,客戶調用再也不用為每一個不同數據結構的菜單編寫一套遍歷的代碼了。

到目前為止,我們做了些什么?

圖片說明
我們現在使用一個共同的迭代器接口(Iteraotr)實現了兩個具體類(PizzaIterator和PancakeIterator)。這兩個具體類都實現了各自的hasNext()方法和next()方法。

? 然后再PancakeHouseMenu和PizzaHouseMenu兩個類中,創建一個createIterator()方法返回各自的迭代器,在Waitress類中,使用這兩個餐廳對象返回的迭代器打印菜單。這時Waitress類和Client類再也不需要關心存放菜單的數據結構,之關心能從迭代器中獲得菜單就好。

? 迭代器模式給你提供了一種方法,可以順序訪問一個聚集對象的元素,而又不用知道內部是如何表示的。你已經在前面的兩個菜單實現中看到了這一點。在設計中使用迭代器的影響是明顯的:如果你有一個統一的方法訪問聚合中的每一個對象,你就可以編寫多態的代碼和這些聚合搭配,使用如同前面的printMenu()方法一樣,只要有了迭代器這個方法根本不用管菜單究竟是由數組還是集合或者其他的數據結構來保存的。

? 另外一個對你的設計造成重要影響的,是迭代器模式把在元素之間游走的責任交給迭代器,而不是聚合對象。這不僅讓聚合的接口和實現變得更簡潔,也可以讓聚合更專注它所應該專注的事情上面,而不必去理會遍歷的事情。

? 讓我們檢查類圖,將來龍去脈拼湊出來……

圖片說明

先看看Aggregate接口,有一個共同的接口提供所有的聚合使用,這對客戶代碼是很方便的,將客戶代碼從集合對象的實現解耦。

? 接下來看看ConcreteAggregate類,這個具體聚合持有一個對象的集合,并實現一個方法,利用此方法返回集合的迭代器。每一個具體聚合都要負責實例化一個具體的迭代器,次迭代器能夠便利對象集合。

? 接下來是Iterator接口,這是所有迭代器都必須實現的接口,它包含一些方法,利用這些方法可以在集合元素之間游走。你可以自己設計或者使用java.util.Iterator接口。

? 最后是具體的迭代器,負責遍歷集合。

單一責任

? 如果我們允許我們的聚合實現他們內部的集合,以及相關的操作和遍歷的方法,又會如何?我們已經知道這回增加聚合中的方法個數,但又怎么樣呢?為什么這么做不好?

? 想知道為什么,首選需要認清楚,當我們允許一個類不但要完成自己的事情,還同時要負擔更多的責任時,我們就給這個類兩個變化的原因。如果這個集合變化的話,這個類也必須要改變,如果我們遍歷的方式改變的話,這個類也必須跟著改變。所以,引出了設計原則的中心:

單一責任:一個類只有一個引起變化的原因。

類的每個責任都有改變的潛在區域。超過一個責任,意味著超過一個改變區域。這個原則告訴我們,盡量讓每一個類保持單一責任。

? 內聚(cohesion)這個術語你應該聽過,它用來度量一個類或者模塊緊密地達到單一目的或責任。

? 當一個模塊或一個類被設計成只支持一組相關的功能時,我們說它具有高內聚;反之,當被設計成支持一組不相關的功能時,我們說它具有低內聚。

? 內聚是一個比單一職責更普遍的概念,但兩者其實關系是很密切的。遵守這個原則的類更容易有很高的凝聚力,而且比背負許多職責的低內聚類更容易維護。

? 以上就是迭代器模式的一些內容。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445447.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445447.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445447.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java內存模型常見問題

1.什么是內存模型&#xff1f; 在多核系統中&#xff0c;處理器一般有一層或者多層的緩存&#xff0c;這些的緩存通過加速數據訪問&#xff08;因為數據距離處理器更近&#xff09;和降低共享內存在總線上的通訊&#xff08;因為本地緩存能夠滿足許多內存操作&#xff09;來提高…

數據結構和算法(03)---棧和隊列(c++)

文章目錄目錄一.棧1.棧的基本操作2.使用C模板類實現棧二.隊列1.隊列的基本操作2.循環隊列**循環隊列順序存儲****循環隊列鏈式存儲**3.雙端隊列目錄 數據結構&#xff1a; 邏輯結構&#xff1a;數組&#xff0c;棧&#xff0c;隊列&#xff0c;字符串&#xff0c;樹&#xff0c…

索尼XB950N1 震撼人心的重低音

雖然題目是震撼人心的重低音&#xff0c;但是低音可以通過app調節&#xff0c;所以我們可以用這個耳機聽各種類型的歌曲。 索尼XB950N1與XB950B1非常相似&#xff0c;但索尼XB950N1提供了主動降噪&#xff0c;續航稍長一些。從藍牙3.0升級到了藍牙4.1&#xff0c;改善了傳輸范…

數據結構和算法(04)---數組,動態內存,vector(c++)

文章目錄目錄數組1.數組的申明2.數組的初始化3.二維數組4.指向數組的指針5.傳遞數組給函數動態內存1.new &#xff0c;delete運算符2.數組的動態內存分配vector1.vector基本操作2.vector使用3.vector動態二維數組 初始化和賦值目錄 數據結構&#xff1a; 邏輯結構&#xff1a;數…

數據結構和算法(05)---鏈表(c++)

文章目錄目錄鏈表的基本概念1.數組和鏈表鏈表的使用1.鏈表的簡單使用2.鏈表的進階使用3.鏈表的高階使用4.鏈表的其他操作鏈表容器list1.list介紹2. list使用3. list與vector之間的區別4.list例子代碼目錄 數據結構&#xff1a; 邏輯結構&#xff1a;數組&#xff0c;棧&#xf…

論文閱讀 狀態壓縮

狀態壓縮 Abstract 信息學發展勢頭迅猛&#xff0c;信息學奧賽的題目來源遍及各行各業&#xff0c;經常有一些在實際應用中很有價值的問題被引入信息學并得到有效解決。然而有一些問題卻被認為很可能不存在有效的(多項式級的)算法&#xff0c;本文以對幾個例題的剖析&#xf…

數據結構和算法(06)---二叉樹(c++)

文章目錄目錄二叉樹1.二叉樹的基本概念2.二叉樹的應用和時間復雜度3.二叉樹的插入4.二叉樹的查找5. 二叉樹的遍歷6.二叉樹的刪除二叉樹的基本操作1.二叉樹的基礎操作2.代碼實現創建二叉樹和三種遍歷二叉樹的方法目錄 數據結構&#xff1a; 邏輯結構&#xff1a;數組&#xff0c…

如何轉載CSDN博客

前言 對于喜歡逛CSDN的人來說&#xff0c;看別人的博客確實能夠對自己有不小的提高&#xff0c;有時候看到特別好的博客想轉載下載&#xff0c;但是不能一個字一個字的敲了&#xff0c;這時候我們就想快速轉載別人的博客&#xff0c;把別人的博客移到自己的空間里面&#xff0c…

程序員歌曲推薦

更多的時候&#xff0c;不光是電腦和鍵盤陪著我們度過一天&#xff0c;耳機和音樂也成了IT人生活中必不可少的一部分 上班和下班的路上&#xff0c;心情失落時&#xff0c;失眠時&#xff0c;甚至是工作時都可能會聽音樂&#xff0c;讓音樂為我們療傷&#xff0c;讓精神得以放…

如何在博客內添加音樂

<center> <iframe border"0" width"480" height"640" src"//music.163.com/outchain/player?type0&amp;id448977181;auto1&amp;height430"> </iframe> </center> 將代碼復制到markdown編輯器里&am…

CSDN寫博客(字體顏色、大小)

markdown里面的標記語言可以使用標簽對來實現對文本文字顏色大小信息的控制。下面給出幾個實例&#xff1a; 黑體字示例 微軟雅黑示例 華文彩云示例 color#00ffff size可以根據實際大小進行設置&#xff0c;一般不超過7。 紅色字體CSDN 紅色字體CSDN 使用十六進制顏色值 …

bose qc30 安靜的城市是什么樣子

使用感受 網友1&#xff08;20歲&#xff09;&#xff1a; 當你帶著這個耳機聽音樂的時候&#xff0c;有一種感覺&#xff0c;感覺這個世界都是你歌曲里的MV&#xff0c;這個枯燥乏味的世界都被賦予了你心中的那份情感&#xff0c;這種感覺&#xff0c;真的很棒 網友2&#…

DeepLearning.ai 提煉筆記(5-1)-- 循環神經網絡

參考博客 Class 5: 序列模型Sequence Models Week 1: 循環神經網絡RNN (Recurrent) 文章目錄Class 5: 序列模型Sequence ModelsWeek 1: 循環神經網絡RNN (Recurrent)目錄序列模型-循環神經網絡1.序列模型的應用2.數學符號3.循環神經網絡模型傳統標準的神經網絡循環神經網絡的…

常見人工智能比賽平臺總結

目錄1.kaggle比賽1.1 kaggle比賽是什么&#xff1f;1.2 為什么舉辦kaggle比賽&#xff1f;1.3 kaggle比賽形式是什么&#xff1f;1.4 kaggle比賽的獎勵制度是什么&#xff1f;2.阿里天池比賽2.1 阿里天池比賽是什么&#xff1f;2.2 為什么舉辦阿里天池比賽&#xff1f;2.3 阿里…

機器學習模型評分總結(sklearn)

文章目錄目錄模型評估評價指標1.分類評價指標acc、recall、F1、混淆矩陣、分類綜合報告1.準確率方式一&#xff1a;accuracy_score方式二&#xff1a;metrics2.召回率3.F1分數4.混淆矩陣5.分類報告6.kappa scoreROC1.ROC計算2.ROC曲線3.具體實例2.回歸評價指標3.聚類評價指標1.…

kaggle (02) - 房價預測案例(進階版)

房價預測案例&#xff08;進階版&#xff09; 這是進階版的notebook。主要是為了比較幾種模型框架。所以前面的特征工程部分內容&#xff0c;我也并沒有做任何改動&#xff0c;重點都在后面的模型建造section Step 1: 檢視源數據集 import numpy as np import pandas as pd讀…

《Head First設計模式》第二章筆記 觀察者模式

背景 客戶有一個WeatherData對象&#xff0c;負責追蹤溫度、濕度和氣壓等數據。現在客戶給我們提了個需求&#xff0c;讓我們利用WeatherData對象取得數據&#xff0c;并更新三個布告板&#xff1a;目前狀況、氣象統計和天氣預報。 WeatherData對象提供了4個接口&#xff1a; …

libsvm總結

1. 訓練 格式&#xff1a;model libsvmtrain(training_label_vector, training_instance_matrix [, libsvm_options]); 這個函數有三個參數&#xff0c;其中 -training_label_vector:訓練樣本的類標&#xff0c;如果有m個樣本&#xff0c;就是m x 1的矩陣&#xff08;類型必須…

《Head First設計模式》第三章筆記 裝飾者模式

裝飾者模式&#xff08;Decorator Pattern) *利用組合&#xff08;composition&#xff09;和委托&#xff08;delegation&#xff09;可以在運行時實現繼承行為的效果&#xff0c;動態地給對象加上新的行為。 *利用繼承擴展子類的行為&#xff0c;是在編譯時靜態決定的&#x…

機器學習中如何解決數據不平衡問題?

文章目錄目錄什么是數據不平衡問題&#xff1f;數據不平衡會造成什么影響&#xff1f;如何處理數據不平衡問題&#xff1f;1、重新采樣訓練集1.1隨機欠抽樣1.2.基于聚類的過采樣2.使用K-fold交叉驗證3.轉化為一分類問題4.組合不同的重采樣數據集5.用不同比例重新采樣6.多模型Ba…