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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445447.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

索尼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…

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; …

《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…

《Head First设计模式》第四章笔记 工厂模式

之前我们一直在使用new操作符&#xff0c;但是实例化这种行为并不应该总是公开的进行&#xff0c;而且初始化经常会造成耦合问题&#xff0c;工厂模式将摆脱这种复杂的依赖&#xff0c;本次内容包括简单工厂&#xff0c;工厂方法和抽象工厂三种情况。 1 2 3 4 5 6 Duck duck&a…

《Head First设计模式》第五章笔记-单件模式

单件模式 定义&#xff1a;确保一个类只有一个实例&#xff0c;并提供全局访问点。 编写格式&#xff1a; 1 2 3 4 5 6 public class MyClass{ private MyClass(){}//构造方法私有化 public static MyClass getInstance(){ //提供全局访问点 return new My…

《Head First设计模式》第六章笔记-命令模式

封装调用-命令模式 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。 本篇中将不再描述书中所引入的“巴斯特家电自动化公司”的遥控器控制案例&#xff0c;而使用简单易懂的餐厅案例。 在开始之前&#xff0c;让我们通过一个现实中的例子来了解命令模式。 理解…

一文读懂机器学习库graphLab

文章目录目录什么是graphlab为什么使用graphlab?如何安装graphlab?graphlab的简单使用。目录 什么是graphlab GraphLab 是由CMU&#xff08;卡内基梅隆大学&#xff09;的Select 实验室在2010 年提出的一个基于图像处理模型的开源图计算框架&#xff0c;框架使用C语言开发实…

《Head First设计模式》第七章-适配器模式、外观模式

适配器模式 适配器模式是什么&#xff0c;你一定不难理解&#xff0c;因为现实中到处都是。比如说&#xff1a; 如果你需要在欧洲国家使用美国制造的笔记本电脑&#xff0c;你可能需要使用一个交流电的适配器…… 当你不想改变现有的代码&#xff0c;解决接口不适配问题&#…