设计模式——迭代器模式

迭代器模式

定义:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节
Java中使用最多的一种模式,答案不是单例模式,也不是工厂模式,更不是策略模式,而是迭代器模式

题外话

学习这个设计模式之后才知道以前写了很多很“辣鸡”的代码,可能有些小伙伴也这样写过代码。下面我就说说我刚提到的“超辣鸡”的代码的一个例子。希望自己,也希望每一个小伙伴都不要再犯这种错误。

需求:图书馆有一些语文文学书。希望设计一个系统,希望同学可以自由的查阅这些书籍的相关情况。

简单的定义一个书籍类:

public class Book {
    private String mName;

    public Book(String name){
        mName = name;
    }

    public String getName() {
        return mName;
    }
}

定义一个语文文学书籍列表,这里只是简单考虑单一情况,不考虑并发多线程。

public class ChineseBooks {
    private List<Book> mBooks;

    private static ChineseBooks sChineseBooks;

    public static ChineseBooks getInstance() {
        if(sChineseBooks == null){
            sChineseBooks = new ChineseBooks();
        }
        return sChineseBooks;
    }

    private ChineseBooks(){
        mBooks = new ArrayList<>();
        mBooks.add(new Book("论语"));
        mBooks.add(new Book("诗经"));
        mBooks.add(new Book("唐诗"));
        mBooks.add(new Book("宋词"));
        mBooks.add(new Book("明清小说"));
        mBooks.add(new Book("近代散文"));
    }

    public List<Book> getBooks() {
        return mBooks;
    }
}

学生查阅书籍清单:

public class TestChineseBook {
    public static void main(String args[]){
        ChineseBooks books = ChineseBooks.getInstance();
        List<Book> list = books.getBooks();
        for(Book book : list){
            System.out.println(book.getName());
        }
    }
}

输出:

论语
诗经
唐诗
宋词
明清小说
近代散文

看上去很正常是不是?但其实这就是超级糟糕的代码设计。
假设某个学生搞破坏

ChineseBooks books = ChineseBooks.getInstance();
        List<Book> list = books.getBooks();
        list.add(new Book("不堪入目的书籍"));

        for(Book book : list){
            System.out.println(book.getName());
        }

然后就输出:

论语
诗经
唐诗
宋词
明清小说
近代散文
不堪入目的书籍

可能有同学不小心:

 ChineseBooks books = ChineseBooks.getInstance();
 List<Book> list = books.getBooks();
 list.clear();

这样的代码设计对于产品是属于毁灭性的。因为直接把整个集合结构给了“外部”,而“外部”又能操作这个集合。

有类似上面经历的小伙伴,请务必仔细学习迭代器模式。

迭代器模式 官方说明

意图:提供一种方法顺序访问一个聚合对象中各个元素,而又无须暴露该对象的内部表示。

主要解决:不同的方式来遍历整个整合对象。

何时使用:遍历一个聚合对象。

如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。

关键代码:定义接口:hasNext, next。

应用实例:JAVA 中的 iterator。

优点

1、它支持以不同的方式遍历一个聚合对象。

2、迭代器简化了聚合类。

3、在同一个聚合上可以有多个遍历。

4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

使用场景

1、访问一个聚合对象的内容而无须暴露它的内部表示。

2、需要为聚合对象提供多种遍历方式。

3、为遍历不同的聚合结构提供一个统一的接口。

注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

注意:很多java集合类已经包含了迭代器( java.util.Iterator),例如ArrayList,就可以通过ArrayList.iterator()拿到相关的迭代器(是正序迭代器)。但下面例子为了更好的理解原理,采用自定义迭代器,但在实际开发中可以直接用Java自带的迭代器

image

利用迭代器实现题外话中的例子

定义一个迭代器

public interface BookIterator {
    boolean hasNext();
    Object next();
}

获取迭代器的容器,保证每次获取的迭代器都是一个新迭代器,所以支持多个迭代器同时访问集合,同时这里也可以定义不同种类的迭代器,让集合有不同的遍历方式


public interface Container {
    BookIterator getIterator();
}

集合类中实现容器

public class NewChineseBooks implements Container {
    private List<Book> mBooks;

    private static NewChineseBooks sChineseBooks;

    public static NewChineseBooks getInstance() {
        if(sChineseBooks == null){
            sChineseBooks = new NewChineseBooks();
        }
        return sChineseBooks;
    }

    private NewChineseBooks(){
        mBooks = new ArrayList<>();
        mBooks.add(new Book("论语"));
        mBooks.add(new Book("诗经"));
        mBooks.add(new Book("唐诗"));
        mBooks.add(new Book("宋词"));
        mBooks.add(new Book("明清小说"));
        mBooks.add(new Book("近代散文"));
    }

    @Override
    public BookIterator getIterator() {
        return new ChineseBookIterator();
    }

    //正序迭代器
    public class ChineseBookIterator implements BookIterator{
        int mIndex = 0;
        @Override
        public boolean hasNext() {
            return mBooks.size() > mIndex;
        }

        @Override
        public Object next() {
            return mBooks.get(mIndex++);
        }
    }
}
public class TestNewChineseBook {
    public static void main(String args[]){
        NewChineseBooks books = NewChineseBooks.getInstance();
        BookIterator iterator = books.getIterator();
        while (iterator.hasNext()){
            System.out.println(((Book)iterator.next()).getName());
        }
    }
}

输出:

论语
诗经
唐诗
宋词
明清小说
近代散文

这样的实现遍历是不会出现题外话中的那种糟糕的情况了。

多种顺序遍历

迭代器支持多种遍历方式,只需要改动Container接口

public interface Container {
    BookIterator getIterator();
    BookIterator getReverseIterator();//新增倒序迭代器
}

实现倒序迭代器

public class NewChineseBooks implements Container {
    private List<Book> mBooks;

    private static NewChineseBooks sChineseBooks;

    public static NewChineseBooks getInstance() {
        if(sChineseBooks == null){
            sChineseBooks = new NewChineseBooks();
        }
        return sChineseBooks;
    }

    private NewChineseBooks(){
        mBooks = new ArrayList<>();
        mBooks.add(new Book("论语"));
        mBooks.add(new Book("诗经"));
        mBooks.add(new Book("唐诗"));
        mBooks.add(new Book("宋词"));
        mBooks.add(new Book("明清小说"));
        mBooks.add(new Book("近代散文"));
    }

    @Override
    public BookIterator getIterator() {
        return new ChineseBookIterator();
    }

    @Override
    public BookIterator getReverseIterator() {
        return new ChineseReverseInterator();
    }

    public class ChineseBookIterator implements BookIterator{
        int mIndex = 0;
        @Override
        public boolean hasNext() {
            return mBooks.size() > mIndex;
        }

        @Override
        public Object next() {
            return mBooks.get(mIndex++);
        }
    }

    /**
     * 实现倒序遍历
     */
    public class ChineseReverseInterator implements BookIterator {
        int mIndex = mBooks.size() - 1;
        @Override
        public boolean hasNext() {
            return mIndex >= 0;
        }

        @Override
        public Object next() {
            return mBooks.get(mIndex--);
        }
    }
}

测试:

public class TestNewChineseBook {
    public static void main(String args[]){
        NewChineseBooks books = NewChineseBooks.getInstance();
        BookIterator iterator = books.getIterator();
        System.out.println("=======正序======");
        while (iterator.hasNext()){
            System.out.println(((Book)iterator.next()).getName());
        }
        System.out.println("\n=======倒序======");
        BookIterator reverse = books.getReverseIterator();
        while (reverse.hasNext()){
            System.out.println(((Book)reverse.next()).getName());
        }
    }
}

输出

=======正序======
论语
诗经
唐诗
宋词
明清小说
近代散文

=======倒序======
近代散文
明清小说
宋词
唐诗
诗经
论语

注意,支持多种顺序访问,就意味要实现多个迭代器,也增加了系统的复杂性

接着题外话举一个经典的迭代器例子

还有一些历史书,但是是基于数组存储的,如下:

public class HistoryBooks {
    private static final HistoryBooks mHistoryBooks = new HistoryBooks();
    private Book[] mBooks;

    private HistoryBooks() {
        mBooks = new Book[]{
                new Book("春秋战国"),
                new Book("秦汉晋南北朝"),
                new Book("隋唐元宋"),
                new Book("明清民国"),
                new Book("新中国")
        };
    }

    public static HistoryBooks getInstance() {
        return mHistoryBooks;
    }
}

通过迭代器实现遍历访问:

public class HistoryBooks implements Container {
    private static final HistoryBooks mHistoryBooks = new HistoryBooks();
    private Book[] mBooks;

    private HistoryBooks() {
        mBooks = new Book[]{
                new Book("春秋战国"),
                new Book("秦汉晋南北朝"),
                new Book("隋唐元宋"),
                new Book("明清民国"),
                new Book("新中国")
        };
    }

    public static HistoryBooks getInstance() {
        return mHistoryBooks;
    }
    @Override
    public BookIterator getIterator() {
        return new HistoryIterator();
    }

    @Override
    public BookIterator getReverseIterator() {
        return null;
    }

    public class HistoryIterator implements BookIterator{
        int mIndex = 0;
        @Override
        public boolean hasNext() {
            return mIndex < mBooks.length;
        }

        @Override
        public Object next() {
            return mBooks[mIndex++];
        }
    }
}

这个例子除了体现了迭代器分离数据结构和外部访问对象的优点外,更体现了统一访问接口的优点:因为用户并不需要了解数据内部如何实现,如何存储,遍历的接口都是一样的,这也降低了系统使用的复杂性,以及体现了接口的统一性。但也体现了一个缺点,就是系统变复杂了,因为增加了一个历史书籍类,就又增加了一个迭代器类,所以集合类增加就要新增一个迭代器

  public class TestNewChineseBook {
    public static void main(String args[]){
        NewChineseBooks books = NewChineseBooks.getInstance();
        BookIterator iterator = books.getIterator();
        while (iterator.hasNext()){
            System.out.println(((Book)iterator.next()).getName());
        }
        System.out.println();
        HistoryBooks historyBooks = HistoryBooks.getInstance();
        BookIterator history = historyBooks.getIterator();
        while (history.hasNext()){
            System.out.println(((Book)history.next()).getName());
        }
    }
}

输出:

论语
诗经
唐诗
宋词
明清小说
近代散文

春秋战国
秦汉晋南北朝
隋唐元宋
明清民国
新中国

回顾优缺点

优点

1、它支持以不同的方式遍历一个聚合对象。
例子中的,正序/倒序,但增加一种方式,就要增加一个迭代器,增加了系统的复杂性

2、迭代器简化了聚合类。
访问者访问每个聚合类的方式都几乎一样,接口统一,自然简化了聚合类

3、在同一个聚合上可以有多个遍历。

4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。增加新的遍历也需要增加新的迭代器,也属于增加了复杂度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值