迭代器模式
定义:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。
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自带的迭代器
利用迭代器实现题外话中的例子
定义一个迭代器
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、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。增加新的遍历也需要增加新的迭代器,也属于增加了复杂度