【一起学系列】之迭代器&组合:虽然有点用不上啦
迭代器模式
意图
提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴漏该对象的内部表示
迭代器模式的诞生
【产品】:嘿,有一个好消息,咱们旗下的餐厅把月巴克的咖啡店吞并了!太棒了!年终奖稳了!
【开发】:Yeah!Yeah!Yeah!
【产品】:但是他们好像反应一个问题,月巴克的点餐系统好像不兼容我们的体系,怎么回事?不就是一个菜单吗?
【开发】:Oh!No!一定它们的 数据结构 不一样导致的,遍历出现了问题!
【产品】:那怎么办?BOSS,你们一起想想办法吧!
【开发】:老大,我们能不能把遍历方法抽取出来啊?我们遍历操作就可以不用考虑各种细节了,只需要管遍历类就好了。
【BOSS】:什么遍历类的,这叫 迭代器 好吗!其实JDK对于迭代器已经维护的很好了,但是咱们这业务也有一点特殊性,就按你说的办吧,办不好的话,刚才说的年终奖就没了。
【开发】:哦,好的(脸上笑嘻嘻,心里MMP)
HeadFirst 核心代码
自己整一个迭代器
定义迭代器持有者 非必须代码
/**
* ******************************
* description: 迭代器持有者
* ******************************
*/
public interface MyContainer {
MyIterator getIterator();
}
迭代器接口
/**
* ******************************
* description: 迭代器接口
* ******************************
*/
public interface MyIterator {
boolean hasNext();
Object next();
}
迭代器工作类 食物菜单
public class FoodRepository implements MyContainer {
String[] names = {"宫保鸡丁", "麻辣香锅", "油闷大虾"};
@Override
public MyIterator getIterator() {
return new NameIterator();
}
private class NameIterator implements MyIterator {
private int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public Object next() {
return hasNext() ? names[index++] : null;
}
NameIterator() {
index = 0;
}
}
}
迭代器工作类 咖啡菜单
public class CoffeeRepository implements MyContainer {
List<String> names = Arrays.asList("雀巢咖啡", "黑糖玛奇朵", "半点寂寞");
@Override
public MyIterator getIterator() {
return new NameIterator();
}
private class NameIterator implements MyIterator {
private int index;
@Override
public boolean hasNext() {
return index < names.size();
}
@Override
public Object next() {
return hasNext() ? names.get(index++) : null;
}
NameIterator() {
index = 0;
}
}
}
测试类 观察调用的表现形式
public class App {
public static void main(String[] args){
// 餐厅菜单
FoodRepository food = new FoodRepository();
MyIterator foodIterator = food.getIterator();
while (foodIterator.hasNext()) {
System.out.println("Food: -> " + foodIterator.next());
}
CodeUtils.spilt();
// 咖啡菜单
CoffeeRepository coffee = new CoffeeRepository();
MyIterator coffeeIterator = coffee.getIterator();
while (coffeeIterator.hasNext()) {
System.out.println("Coffee: -> " + coffeeIterator.next());
}
}
}
JDK中的迭代
public class App {
public static void main(String[] args){
// JDK
List<String> names = Arrays.asList("Han", "John", "Tomams");
Iterator<String> iterable = names.iterator();
while (iterable.hasNext()) {
System.out.println("JDK Iterator: -> " + iterable.next());
}
CodeUtils.spilt();
// JDK
names.forEach(s -> System.out.println("JDK forEach: -> " + s));
}
}
因此对于业务上没有什么要求且常见的数据结构,我们不再需要自行定义迭代器
迭代器模式的设计思路:
- Iterator 迭代器
- Concretelterator 具体迭代器
- Aggregate 集合
- ConcreteAggregate 具体集合
简单来说,
- 我们需要明确集合的类型(数组,链表,Map,树结构或者普通List)
- 我们需要定义迭代器的行为,是否有下一个(遍历完成),取值,移除等等
- 遍历的行为或者算法在具体的迭代器中实现,根据不同的数据结构和业务要求完成编码,实现访问一致,但细节不同的效果
如果看着有点模棱两可,就看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面
遵循的设计原则
- 单一职责原则
说明:迭代器类在设计中仅仅包含集合迭代的作用,它是把原本数据结构中的遍历抽取出来,达到 高内聚 的效果。
所谓高内聚:当一个模块或一个类被设计成只支持一组相关功能时,我们说它具有 高内聚 的特征。
什么场景适合使用
- 访问一个聚合对象的内容而无需暴漏它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供一个统一的接口
Code/生活中的实际应用
举一个不是很恰当的例子,我们都用自动贩卖机买过水,付钱之后它会自动滚出来,大家有没有想过它是怎么实现这个效果的呢?它支持瓶装的,罐装的,甚至还支持袋装的,方便面,口红等等五花八门的产品,它的内部结构可能都各不相同,但是最终的表现效果就是我们直接从出口处拿即可,这是不是迭代器模式的一种体现呢?
迭代器模式UML图
组合模式
意图
将对象组合成树形结构以表示 “部分-整体” 的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性
说人话:想想Java里的File类
组合模式的误区
组合模式 不是 一堆模式的组合!
组合模式的诞生
【开发】:老大,我在写菜单类的时候感觉好痛苦啊!
【BOSS】:怎么了?
【开发】:菜单有真正的菜品,还有父级菜单啊,它们俩得维护两套逻辑,混在一起好难受!
【BOSS】:你在操作文件的时候怎么不觉得难受?你咋不动动脑子想着抽象一下啊!
【开发】:对啊!我去改代码!
HeadFirst 核心代码
定义抽象行为
/**
* ******************************
* description: 定义抽象行为
* ******************************
*/
public abstract class MenuComponent {
public String name;
/***
* 添加
*/
public abstract void add(MenuComponent component) throws Exception;
/***
* 移除
*/
public abstract void remove(MenuComponent component) throws Exception;
/***
* 获取菜单名
*/
public abstract String getName();
/***
* 获取子菜单
*/
public abstract MenuComponent getChild(int i) throws Exception;
/***
* 打印菜单
*/
public abstract void print();
}
实现 “整体”
public class Menu extends MenuComponent{
List<MenuComponent> menuComponents = new ArrayList<>();
public Menu(String name) {
this.name = name;
}
@Override
public void add(MenuComponent component) {
this.menuComponents.add(component);
}
@Override
public void remove(MenuComponent component) {
this.menuComponents.remove(component);
}
@Override
public String getName() {
return this.name;
}
@Override
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
@Override
public void print() {
System.out.println("当前菜单项: " + getName());
for (MenuComponent component : menuComponents) {
component.print();
}
}
}
实现 "部分"
public class MentItem extends MenuComponent{
public MentItem(String name) {
this.name = name;
}
@Override
public void add(MenuComponent component) throws Exception {
throw new Exception("无法添加");
}
@Override
public void remove(MenuComponent component) throws Exception {
throw new Exception("无法移除");
}
@Override
public String getName() {
return this.name;
}
@Override
public MenuComponent getChild(int i) throws Exception {
throw new Exception("无子节点");
}
@Override
public void print() {
System.out.println(" 食物名: " + getName());
}
}
测试类
public class App {
/***
* 推荐代码阅读顺序:
*
* @see MenuComponent
* @see Menu
* @see MentItem
*/
public static void main(String[] args) {
Menu meat = new Menu("炒菜类");
MentItem item1 = new MentItem("宫保鸡丁");
MentItem item2 = new MentItem("剁椒鸡蛋");
MentItem item3 = new MentItem("鱼香肉丝");
Menu vegetable = new Menu("素食");
MentItem v1 = new MentItem("酸辣土豆丝");
MentItem v2 = new MentItem("爆炒包菜");
meat.add(item1);
meat.add(item2);
meat.add(item3);
vegetable.add(v1);
vegetable.add(v2);
meat.add(vegetable);
meat.print();
}
}
/***
* 输出内容:
*
* 当前菜单项: 炒菜类
* 食物名: 宫保鸡丁
* 食物名: 剁椒鸡蛋
* 食物名: 鱼香肉丝
* 当前菜单项: 素食
* 食物名: 酸辣土豆丝
* 食物名: 爆炒包菜
*/
组合模式的设计思路:
- Component 为组合的对象声明接口或抽象类
- Leaf 叶子节点(最小单元)
- Composite 组合节点(即还有子节点的节点)
- Client 客户端,调用方
简单来说,
- 当我们需要树形结构时,抽象叶子节点和组合节点(有子节点的节点)的共同行为
- 让两者实现同一个接口
如果看着有点模棱两可,就看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面
什么场景适合使用
- 需要表示对象的部分-整体层次结构
- 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中所有对象
Code/生活中的实际应用
依然是一个不太恰当的例子,我们在操作文件和文件夹的时候,都有其移动,复制,重命名,查看文件大小等等功能,对于Java来说,它的底层实现是有一个 是否是文件夹
的方法来区分,但实际上这也是组合模式的根本思想,即对于表示 部分 的对象,和 整体 的对象,拥有统一的操作行为
组合模式的UML图
总结
- 迭代器模式:该模式在JDK中已经封装的非常好,我们其实不太需要再自行处理,不过在处理特殊数据结构时这种统一操作的思想仍然值得借鉴
- 组合模式:组合模式仅在需要树形结构的场景下可发挥巨大的作用,同样的,它规范不同类型对象的行为,统一操作的思想,值得我们借鉴
相关代码链接
- 兼顾了《HeadFirst》以及《GOF》两本经典书籍中的案例
- 提供了友好的阅读指导