设计模式之迭代器与组合模式

《Head First 设计模式》阅读笔记

Posted by wangtiegang on March 22, 2020

迭代器模式

设计模式这一章的内容很长,其中迭代器模式很常用,而组合模式却很少碰到,首先是迭代器模式,经常在 java 集合类中使用迭代器,但是却不知道这是一种设计模式,也没有去关注底层实现,看完书本之后,有种原来是这一的感觉。

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

迭代器模式可以很好的隐藏内部的数据结构,让用户在遍历集合的时候与内部数据结构解藕。同时也可以以统一的方式去访问不同集合中的元素,比如数组通过下标直接获取,List通过 get 方法获取,如果使用迭代器,就可以都使用 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
 * 迭代模式接口
 */
public interface Iterator {

    /**
     * 判断是否还有下一个元素
     * @return
     */
    boolean hasNext();

    /**
     * 返回下一个元素
     * @return
     */
    Object next();

}

/**
 * 迭代器实现类,不同集合对象需要实现自己的迭代器
 */
public class CollectionIterator implements Iterator {

    /**
     * 记录下一个元素的位置
     */
    private int index = 0;

    /**
     * 要迭代的集合
     */
    private Object[] objects;

    public CollectionIterator(Object[] objects){
        this.objects = objects;
    }

    @Override
    public boolean hasNext() {
        if(index >= objects.length || objects[index] == null){
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        Object object = objects[index];
        index = index + 1;
        return object;
    }

}

/**
 * 集合对象
 */
public class Collection {
    /**
     * 集合内部数据结构,比如数组,List,Set等
     */
    private Object[] objects = new Object[16];

    /**
     * 创建迭代器,这样外部就可以以统一的方式遍历元素,而不用暴露内部的实现
     * @return
     */
    public Iterator createIterator(){
        return new CollectionIterator(objects);
    }

    public void add(Object object){
        // 添加元素
    }

    public Object get(int i){
        //获取元素
        return objects[i];
    }

}

以上就是一个非常简单的迭代器了,在实际开发中,我们可以直接使用 java.util 中的 Iterator 接口,而不需要自己定义,java也提供了 for ( : ) 方式去隐式调用迭代器。

组合模式

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

组合模式这个不是很好理解,通常就是以树形方式创建对象的结构,树里面包含了组合对象以及个别的对象。这个组合对象和个别对象实现了同样的接口,组合对象可以具有个别对象的子节点。个人理解就是这种结构存储有层级关系的数据,比如菜单,菜单下有子菜单,子菜单下还有子子菜单,这个结构在后台管理系统中还挺常见的,比如部门,成本中心等等。通过组合模式,操作树中的对象时,就不用去判断它到底是叶子还是层级节点,可以使用统一的方法,因为它们实现了一样的接口。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import java.util.List;

/**
 * 组合接口,规范组合/个体的操作方式
 * 默认 throw 异常,因为每个方法对组合/个体的意义是不一样的
 */
public abstract class MenuComponent {

    /**
     * 组合方法
     */
    public void add(MenuComponent component){
        throw new UnsupportedOperationException();
    }

    /**
     * 组合/个体方法
     * @return
     */
    public void printName(){
        throw new UnsupportedOperationException();
    }

    /**
     * 组合方法
     * @return
     */
    public List<MenuComponent> getChild() {
        throw new UnsupportedOperationException();
    }

}

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 组合对象,重写组合的所有方法
 */
public class Menu extends MenuComponent {

    private String name;

    /**
     * 组合对象还会具有自己的子代
     */
    private List<MenuComponent> child = new ArrayList<>();

    public Menu(String name){
        this.name = name;
    }

    @Override
    public void add(MenuComponent component) {
        this.child.add(component);
    }

    /**
     * 打印自身,以及利用迭代器和递归打印后代
     */
    @Override
    public void printName() {
        System.out.println(name);
        Iterator iterator = child.iterator();
        while (iterator.hasNext()){
            MenuComponent component = (MenuComponent) iterator.next();
            component.printName();
        }
    }

    @Override
    public List<MenuComponent> getChild() {
        return child;
    }

}

/**
 * 个体对象
 */
public class MenuItem extends MenuComponent {

    private String name;

    public MenuItem(String name){
        this.name = name;
    }

    /**
     * 只重写个体的方法,打印自身
     * @return
     */
    @Override
    public void printName() {
        System.out.println(name);
    }

}

public class SysMenuBar extends MenuComponent{

    public static void main(String[] args) {
        // 构造一个层级菜单
        MenuComponent rootMenu = new Menu("root");
        rootMenu.add(new MenuItem("root-a"));
        rootMenu.add(new MenuItem("root-b"));
        rootMenu.add(new MenuItem("root-c"));

        MenuComponent menu_d = new Menu("root-d");
        menu_d.add(new MenuItem("root-d-a"));
        menu_d.add(new MenuItem("root-d-b"));
        menu_d.add(new MenuItem("root-d-c"));
        rootMenu.add(menu_d);

        // 通过调用根节点的方法,实现遍历所有后代的方法
        rootMenu.printName();

    }

}

可以看出利用组合和迭代器,可以很方便的遍历树中的所有元素,对组合和个体的操作方法是一样的,这样就不用在 printName 时判断它到底是组合还是个体了。

看完整章,感觉迭代器很好理解,组合模式要深度应用还没什么想法,不过后续在代码中看到类似的场景就可以有个概念了,可以尝试去设计更好扩展的代码。