迭代器与组合模式

面对不同的问题,自然会用到不同的数据结构,甚至相同的问题也可以用不同的数据结构来实现。比如数组和ArrayList都可以构造一个列表。

但当想获得集合内的元素时,直接的取用就会涉及到集合内部的具体实现,而这些往往不是我们希望暴露给外部的。因此,可以为集合设计迭代器来遍历集合内元素,随后只需向外部提供迭代器,就可以隐藏内部的实现。

迭代器模式

考虑两个餐厅的菜单的合并,其中一个使用数组保存菜单,而另一个使用ArrayList保存菜单。而两个餐厅都不想修改自己的数据结构,因为会涉及大量的代码改动(这同样适用于现实中的其他问题)。

package collection;

import java.util.Iterator;

public class DinerMenu {

static final int MAX_ITEMS = 6;

int numberOfItems = 0;

MenuItem[] menuItems;

public DinerMenu() {

menuItems = new MenuItem[MAX_ITEMS];

addItem("Vegetarian BLT",

"(Fakin') Bacon with lettuce & tomato on whole wheat",

true,

2.99);

addItem("BLT",

"Bacon with lettuce & tomato on whole wheat",

false,

2.99);

addItem("Soup of the day",

"Soup of the day, with a side of potato salad",

false,

3.29);

addItem("Hotdog",

"A hot dog, with saurkraut, relish, onions, topped with cheese",

false,

3.05);

}

public void addItem(String name,

String description,

boolean vegetarian,

double price) {

MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

if (numberOfItems >= MAX_ITEMS) {

System.err.println("Sorry, menu is full! Can't add item to menu!");

} else {

menuItems[numberOfItems] = menuItem;

numberOfItems++;

}

}

public MenuItem[] getMenuItems() {

return getMenuItems;

}

}

package collection;

import java.util.ArrayList;

import java.util.Iterator;

public class PancakeHouseMenu {

ArrayList menuItems;

public PancakeHouseMenu() {

menuItems = new ArrayList();

addItem("K&B's Pancake Breakfast",

"Pancakes with scrambled eggs, and toast",

true,

2.99);

addItem("Regular Pancake Breakfast",

"Pancakes with fried eggs, sausage",

true,

2.99);

addItem("Blueberry Pancake Breakfast",

"Pancakes made with fresh blueberries",

true,

3.49);

addItem("Waffles",

"Waffles, with your choice of blueberries or strawberries",

true,

3.59);

}

public void addItem(String name,

String description,

boolean vegetarian,

double price) {

MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

menuItems.add(menuItem);

}

public ArrayList getMenuItems() {

return menuItems;

}

}

如果直接地获取两个餐厅的菜单,就需要用两个循环,在每个循环中针对其中一个餐厅的内部实现来编程。这会造成大量的重复代码。并且,如果某个餐厅改变的内部实现,每个关于其的循环内的代码都需要修改。

为了不暴露内部实现,应该设计迭代器来遍历集合。

package collection;

import java.util.Iterator;

public class DinerMenuIterator implements Iterator {

MenuItem[] items;

int position = 0;

public DinerMenuIterator(MenuItem[] items) {

this.items = items;

}

@Override

public Object next() {

MenuItem menuItem = items[position];

position++;

return menuItem;

}

@Override

public boolean hasNext() {

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

return false;

} else {

return true;

}

}

}

package collection;

import java.util.ArrayList;

import java.util.Iterator;

public class PancakeHouseIterator implements Iterator {

ArrayList items;

int position = 0;

public PancakeHouseIterator(ArrayList items) {

this.items = items;

}

@Override

public Object next() {

MenuItem menuItem = (MenuItem) items.get(position);

position++;

return menuItem;

}

@Override

public boolean hasNext() {

if (position >= items.size()

|| items.get(position) == null) {

return false;

} else {

return true;

}

}

}

现在可以移除两个餐厅类中直接获取内部集合的方法,用获取迭代器的方法代替。

但实际上,Java中已经提供Iterator接口,因此对于已经存在可用迭代的ArrayList,不必特地为其设计迭代器。而为了满足删除菜单中元素的要求,还需要在数组实现的餐厅的迭代器中实现remove方法。

@Override

public void remove() {

if (position <= 0) {

throw new IllegalStateException(

"You can't remove an item until you've done at least one next()"

);

}

if (items[position-1] != null) {

for (int i = position-1; i < items.length-1; i++) {

items[i] = items[i+1];

}

items[items.length-1] = null;

}

}

现在显示菜单的方法仍然是针对具体的餐厅菜单类实现的,需要设计一个菜单接口类来抽象它们。

package collection;

import java.util.Iterator;

public class Waiter {

Menu pancakeHouseMenu;

Menu dinerMenu;

public Waiter(Menu pancakeHouseMenu, Menu dinerMenu) {

this.pancakeHouseMenu = pancakeHouseMenu;

this.dinerMenu = dinerMenu;

}

public void printMenu() {

Iterator pancakeIterator = pancakeHouseMenu.createIterator();

Iterator dinerIterator = dinerMenu.createIterator();

System.out.println("MENU\n----\nBREAKFAST");

printMenu(pancakeIterator);

System.out.println("\nLUNCH");

printMenu(dinerIterator);

}

private void printMenu(Iterator iterator) {

while (iterator.hasNext()) {

MenuItem menuItem = (MenuItem) iterator.next();

System.out.print(menuItem.getName() + ", ");

System.out.print(menuItem.getPrice() + " -- ");

System.out.println(menuItem.getDescription());

}

}

}

现在我们成功地把具体的实现和调用的方法分开了,显示菜单的方法不关注餐厅菜单的具体数据结构。

设计原则

单一责任原则:一个类应该只有一个引起变化的原因。

当一个类具有多个改变的原因时,在未来修改该类的代码的概率就会很高,维护成本高。

针对这种问题,应该尽量让每个类保持单一的责任,控制改变的原因。

内聚是一个比单一责任原则更普遍的的概念,当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,说它具有低内聚。高内聚的类更加容易维护。

组合模式

使用迭代器模式的餐厅合并已经有了不错的效率,如果有更多的餐厅想要加入,为了方便,也可以用ArrayList来保存不同餐厅的菜单,然后在先显示菜单的方法中遍历这些菜单即可。

但这样的结构仍然缺乏弹性:无法处理嵌套的子菜单。

因此必须使用树的结构来嵌套菜单。这种层次结构能让客户以一致的方式处理个别对象以及对象组合,称作组合模式。

通过组合模式,对象的组合和个别对象之间的差别就被忽略了。

package collection;

public abstract class MenuComponent {

public void add(MenuComponent menuComponent) {

throw new UnsupportedOperationException();

}

public void remove(MenuComponent menuComponent) {

throw new UnsupportedOperationException();

}

public MenuComponent getChild(int i) {

throw new UnsupportedOperationException();

}

public String getName() {

throw new UnsupportedOperationException();

}

public String getDescription() {

throw new UnsupportedOperationException();

}

public double getPrice() {

throw new UnsupportedOperationException();

}

public boolean isVegetarian() {

throw new UnsupportedOperationException();

}

public void print() {

throw new UnsupportedOperationException();

}

}

首先编写一个树结点的抽象类,该类提供了叶节点和组合节点需要实现的方法。

然后修改MenuItem类,令其继承MenuComponent类,作为叶节点,并额外实现print方法。

@Override

public void print() {

System.out.print(" " + getName());

if (isVegetarian()) {

System.out.print("(v)");

}

System.out.println(", " + getPrice());

System.out.println(" -- " + getDescription());

}

修改Menu类,令其继承MenuComponent类,作为组合节点,并额外实现print方法和getChild方法以及add、remove等方法。此时Menu类不再是接口,而是一种组合节点类。

package collection;

import java.util.ArrayList;

import java.util.Iterator;

public class Menu extends MenuComponent {

ArrayList menuComponents = new ArrayList();

String name;

String description;

public Menu(String name, String description) {

this.name = name;

this.description = description;

}

@Override

public void add(MenuComponent menuComponent) {

menuComponents.add(menuComponent);

}

@Override

public void remove(MenuComponent menuComponent) {

menuComponents.remove(menuComponent);

}

@Override

public MenuComponent getChild(int i) {

return (MenuComponent) menuComponents.get(i);

}

@Override

public String getName() {

return name;

}

@Override

public String getDescription() {

return description;

}

@Override

public void print() {

System.out.print("\n" + getName());

System.out.println(", " + getDescription());

System.out.println("---------------------");

Iterator iterator = menuComponents.iterator();

while (iterator.hasNext()) {

MenuComponent menuComponent = (MenuComponent) iterator.next();

menuComponent.print();

}

}

}

现在实现Waiter类十分简单,只需要保存一个根节点,然后调用根节点的print即可。根节点在打印的自己的过程中会递归地调用子节点的print方法。注意,此时我们不需要原来的餐厅菜单类了,抽取其中的餐品信息即可。

package collection;

public class Waiter {

MenuComponent allMenus;

public Waiter(MenuComponent allMenus) {

this.allMenus = allMenus;

}

public void printMenu() {

allMenus.print();

}

}

虽然组合模式在管理层次的同时也执行了菜单的操作,但换取得到了透明性。现在用户可以将组合节点和叶节点视作相同的节点。

组合迭代器

如果不想每次都输出菜单中所有的内容,而是有条件地筛选,就需要使用迭代器来遍历,组合模式也很好地兼容了迭代器的使用。在组合节点的迭代器中递归地使用子节点的迭代器即可。

package collection;

import java.util.Iterator;

import java.util.Stack;

public class CompositeIterator implements Iterator {

Stack stack = new Stack();

public CompositeIterator(Iterator iterator) {

stack.push(iterator);

}

@Override

public Object next() {

if (hasNext()) {

Iterator iterator = (Iterator) stack.peek();

MenuComponent component = (MenuComponent) iterator.next();

if (component instanceof Menu) {

stack.push(component.createIterator());

}

return component;

} else {

return null;

}

}

@Override

public boolean hasNext() {

if (stack.empty()) {

return false;

} else {

Iterator iterator = (Iterator) stack.peek();

if (!iterator.hasNext()) {

stack.pop();

return hasNext();

} else {

return true;

}

}

}

}

组合节点的迭代器要使用上面这种新类型。这种迭代器深度优先地遍历组合节点内的所有元素。

package collection;

import java.util.Iterator;

public class NullIterator implements Iterator {

@Override

public Object next() {

return null;

}

@Override

public boolean hasNext() {

return false;

}

@Override

public void remove() {

throw new UnsupportedOperationException();

}

}

叶节点的迭代器使用上面这种空迭代器,这是为了避免用户需要判断是否真正获取了迭代器。

现在我们来用迭代器显示素食菜单。

public void printVegetarianMenu() {

Iterator iterator = allMenus.createIterator();

System.out.println("\nVEGETARIAN MENU\n----");

while (iterator.hasNext()) {

MenuComponent menuComponent = (MenuComponent) iterator.next();

try {

if (menuComponent.isVegetarian()) {

menuComponent.print();

}

} catch (UnsupportedOperationException e) {}

}

}

迭代器 组合模式 java_设计模式(九)迭代器模式与组合模式相关推荐

  1. java的visitor模式_java设计模式(二十一)访问者模式(Visitor)

    介绍 访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作.访问者模式是一种对象行为型模式. 简单来 ...

  2. java实现备忘录模式_设计模式之第17章-备忘录模式(Java实现)

    设计模式之第17章-备忘录模式(Java实现) 好男人就是我,我就是曾小贤.最近陈赫和张子萱事件闹得那是一个沸沸扬扬.想想曾经每年都有爱情公寓陪伴的我现如今过年没有了爱情公寓总是感觉缺少点什么.不知道 ...

  3. java架构模式与设计模式(九)--一文了解原生云

    原文链接 目录 前言 后端架构演化史 集中式架构 分布式系统架构 容器技术新纪元 Docker 微服务架构 Kubernetes Service Mesh 总结 云原生 Cloud Native 什么 ...

  4. php注册树模式,PHP设计模式之详记注册树模式

    一.什么是注册树模式 注册树模式又叫注册模式.注册器模式.注册树模式是将经常使用到的对象实例挂到一颗全局的树上,需要使用时从数树上取出即可. 举个栗子:有一个空的工具箱.需要维修东西,因此买了扳手和螺 ...

  5. java设计模式中不属于创建型模式_Java设计模式(5)——创建型模式之建造者模式(Builder)...

    一.概述 概念 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示.(与工厂类不同的是它用于创建复合对象) UML图 主要角色 抽象建造者(Builder)--规范建造方法与结果返还 ...

  6. 米线店结账程序 装饰着模式_设计模式(三)装饰者模式

    装饰者模式是以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案.装饰者模式动态地将责任附加到对象身上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案,比生成子类更加灵活. 通常在继承关系 ...

  7. java 装饰器模式_Java设计模式(9)----------装饰器模式

    1.介绍 装饰器模式是一种结构型的设计模式.使用该模式的目的是为了较为灵活的对类进行扩展,而且不影响原来类的结构.有同学说可以通过继承的方式进行实现啊,没错,继承的确可以实现,但是继承的成本相对比较高 ...

  8. php 抽象工厂模式,PHP设计模式(三)抽象工厂模式(Abstract Factory)

    一.什么是抽象工厂模式 抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足以下条件: 系统中有多个产品族,而系统一次只可能消费其中一族产品. 同 ...

  9. Java设计模式之结构型:组合模式

    前言: 我们对于上面两幅图片肯定非常熟悉,这两幅图片我们都可以看做是一个文件结构,对于这样的结构我们称之为树形结构.在数据结构中我们知道可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可 ...

最新文章

  1. 华为总裁任正非谈企业管理:正确的方向来自于妥协
  2. Nature封面:每天工作21.5小时的AI化学家,8天内完成688个实验,已自主发现一种全新催化剂...
  3. 编译hadoop2.2.0源码时报错
  4. java,阳历转阴历
  5. NLP研究方向的「情感分析领域」的简单调研
  6. php权限在哪设置,php如何设置管理员权限
  7. C++添加一个头文件和extern以及全局变量和局部变量问题(清C++蓝书16.3.19上机的一小题)...
  8. 常用的C++ STL
  9. bugku 杂项 流量分析(cnss)
  10. krita绘图_如何使用Krita制作动画视频
  11. 前端JSON格式化显示
  12. C语言用随机函数做猜拳游戏,c语言猜拳游戏
  13. 算法-枚举法-已知xyz + yzz = 532,其中x、y、z都是数字(0~9),编写一个程序求出x、y、z分别代表什么数字。
  14. Javascript正则表达式常用的验证(验证手机号,电话,邮箱,网址等)
  15. 前端生成二维码 微信小程序
  16. PostgreSQL11 MYSQL_安装postgresql11.5
  17. 从视频中提取音频Python
  18. python 密码验证
  19. kissme病毒原理描述及python清除脚本
  20. kdj指标主要看哪个值_KDJ指标的J值与D值差别

热门文章

  1. 给定两个均不超过9的正整数a和n,要求编写程序求a+aa+aaa++⋯+aa⋯a(n个a)之和。
  2. 苹果小程序上下拉出现留白情况
  3. 深拷贝和浅拷贝的理解与应用
  4. uni-app项目构建与实践的思考(持续更新)
  5. Peterson‘s算法(并发双线程互斥锁
  6. python 的scrapy框架
  7. 我朋友搞得一本时尚摄影杂志,正!!
  8. python之路目录_python笔记目录
  9. Windows 危险的注册表键
  10. 魔兽怀旧服服务器紧急维护,魔兽怀旧服紧急维护14小时,玩家们纷纷开启吃瓜模式...