迭代器模式

因为这一章涉及到两个模式,内容有点多,还有一个组合模式留到下一篇写吧。

有许多种方法可以把对象堆起来成为一个集合(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号""牛肉煎饼"false2.99);

        addItem("煎饼2号""素食煎饼"true1.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号""素食披萨"true4.99);

        addItem("披萨2号""海鲜蛤蜊披萨"true5.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号""牛肉煎饼"false2.99);

        addItem("煎饼2号""素食煎饼"true1.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)这个术语你应该听过,它用来度量一个类或者模块紧密地达到单一目的或责任。

​ 当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚。

​ 内聚是一个比单一职责更普遍的概念,但两者其实关系是很密切的。遵守这个原则的类更容易有很高的凝聚力,而且比背负许多职责的低内聚类更容易维护。

​ 以上就是迭代器模式的一些内容。

《Head First设计模式》第九章(1)迭代器模式相关推荐

  1. Head First设计模式读书笔记八 第九章上 迭代器模式

    之前的总结: https://blog.csdn.net/u011109881/article/details/59677544 个人觉得本章节,HeadFirst讲的没有之前看到的网站讲的清晰,至少 ...

  2. 《HeadFirst设计模式》第九章-2组合模式

    1.声明 设计模式中的设计思想.图片和部分代码参考自<Head First设计模式>,作者Eric Freeman & Elisabeth Freeman & Kathy ...

  3. Head First设计模式读书笔记八 第九章下 组合模式

    之前的总结链接: https://blog.csdn.net/u011109881/article/details/58710579 对比headFirst书中的例子,我觉得书中的组合模式的例子比上面 ...

  4. 《Head first设计模式》学习笔记 – 迭代器模式

    迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示. 爆炸性新闻:对象村餐厅和对象村煎饼屋合并了! 真是个好消息!现在我们可以在同一个地方,享用煎饼屋美味的煎饼早餐,和好吃 ...

  5. C#设计模式之十五迭代器模式(Iterator Pattern)【行为型】

    一.引言 今天我们开始讲"行为型"设计模式的第三个模式,该模式是[迭代器模式],英文名称是:Iterator Pattern.还是老套路,先从名字上来看看."迭代器模式& ...

  6. 大战设计模式【12】—— 迭代器模式

    迭代器模式(Iterator) 设计模式使用的例子https://github.com/LinkinStars/DesignPatternsAllExample 一.定义 提供一种方法顺序访问一个聚合 ...

  7. 设计模式笔记十七:迭代器模式

    原文:http://www.runoob.com/design-pattern/ 少许个人理解,如有错误请指出.欢迎一起讨论. 迭代器模式(Iterator Pattern) 是 Java 和 .Ne ...

  8. 设计模式之禅【迭代器模式】

    真刀实枪之迭代器模式 从整理项目说起 老板要看项目,但是有点乱,让我整理下,简单,说干就干 类图先上 代码跟上 IProject package com.peng.ddq;/*** @author k ...

  9. 结合项目实例 回顾传统设计模式(九)迭代器模式

    关于迭代器模式,我们所需要知道的第一件事情就是它依赖于一个名为迭代器的接口.一旦我们有了这个接口,就可以为各种那个对象集合实现迭代器:数组.列表.散列表. 项目实例我们就拿用户角色模块来举例 背景 1 ...

  10. 设计模式的理解:迭代器模式(Iterator)

    迭代器模式,又称游标模式,提供一种方法顺序访问一个集合对象中的各个元素,而又不需要暴露该对象的内部表示. template<typename T> class Iterator{publi ...

最新文章

  1. Shadow Properties之美(一)【Microsoft Entity Framework Core随笔】
  2. 英语规则动词过去式加“ed”后的发音规则
  3. UE4--多线程的实现方式
  4. Java捕获异常密码_Java捕获异常的问题
  5. pywin32官方说明文档_为什么你应该看官方文档而不是搜索博客文章
  6. 双十一终极预告:免单+半价+100% 中奖,没有套路,直降直减!
  7. 安装和部署企业程序库
  8. 结构化分析方法是一种自下而上逐步求精的分析方法【软件项目管理】
  9. Futter基础第6篇: 实现网格布局【GridView、GridView.count、GridView.builder】
  10. poj 3074(DLX)
  11. 容器技术Docker K8s 14 容器服务ACK基础与进阶-容器网络管理
  12. maya动画镜像_Maya
  13. CRACK小试牛刀:关于GALGAME银色遥远爆破记录
  14. 计算机专业ib选课,IB 课程里,总算发现一个貌似容易的学科了!
  15. iPhone 检测 iPhone X 设备的几种方式和分辨率终极指南
  16. 电驴链接服务器老是无响应,全部服务器无响应!!!
  17. LWN:替换 congestion_wait()!
  18. 笔记本插入耳机没反应 必须重启前插入再启动才行 启动后拔下再插入依旧外放
  19. 4.文本分类——textRNN模型
  20. 台式计算机 主控芯片型号,win10系统查看U盘的主控芯片型号的图文方法

热门文章

  1. Target “xxx” links to target “Boost::filesystem“ but the target was not found
  2. CPU8085 8086名字的由来
  3. Linux NULL定义
  4. android4.0.3去掉底部状态栏statusbar,全屏显示示例代码
  5. diff算法_vue源码解读 diff算法
  6. Linux显示txt文件,如何在Linux中显示文本文件中的某些行?
  7. arcgis投影数据计算度分秒
  8. !Spring Aop中四个重要概念,切点,切面,连接点,通知
  9. 第八节: EF的性能篇(一) 之 EF自有方法的性能测试
  10. 【转】abp vNext微服务框架分析