1. 程式人生 > >【設計模式】Iterator迭代器設計模式(容器和容器的遍歷)

【設計模式】Iterator迭代器設計模式(容器和容器的遍歷)

在遍歷容器元素的時候,有很多初學者在疑惑,為什麼返回一個iterator我就能夠去遍歷這個容器了呢?

今天我們就來深入剖析一下迭代器iterator的設計模式(循序漸進的剖析,一定要耐心看完)

iterator是"四人幫"所定義的23種設計模式之一(不太難,也不是非常重要,只是在遍歷容器的時候能夠用到)

首先需要讀這個總結的同志掌握面向物件的思想。

1.我們先自己寫一個可以動態新增物件的容器
建立一個新的Java工程:Iterator
建立一個包:cn.edu.hpu.iterator
在包下建立類ArrayList:
我們要在容器內首先新增一個add方法,用於向容器內新增元素
再寫一個size方法,用於查詢容器元素個數

ArrayList.java:
package cn.edu.hpu.iterator;

public class ArrayList {
	//我們要用陣列模擬一個可以新增任意大小元素的容器
	//首先我們固定一個數組空間,再新增元素的時候,我們去判斷
	//這個空間是不是滿了,如果滿了再在此基礎上再新增一個固定空間
	//往下依次類推,這樣就有了可以新增任意大小元素的容器
	//當然,容器大小一定不可能超過計算機記憶體的大小
	Object[] objects=new Object[10]; 
	int index=0;//代表objects的下一個空的位置在哪裡
	
	//新增元素方法
	public void add(Object o){
		if(index==objects.length){
			//原來的陣列已滿,要做擴充套件(原長度X2)
			Object[] newObjects=new Object[objects.length*2];
			//把objects內容拷貝到新的陣列newObjects上
			System.arraycopy(objects, 0, newObjects, 0, objects.length);//陣列拷貝函式
			objects=newObjects;//之後把newObjects拷貝給objects陣列
		}
		objects[index]=o;
		index++;
	}
	
	//獲取容器元素個數方法
	public int size(){
		return index;
	}
}

測試:

package cn.edu.hpu.iterator;

import cn.edu.hpu.iterator.ArrayList;

public class ArrayListTest {
	public static void main(String[] args) {
		ArrayList al=new ArrayList();
		for(int i=0;i<20;i++){
			al.add(new Object());
		}
		System.out.println(al.size());
	}
}
測試結果:20

我們就完成了這樣一個容器,這個容器是用陣列來實現的。容器比陣列好在哪裡呢?使用容器就不需要考慮到陣列的邊界問題了,容器可以動態拓展。想往裡面裝就add就行了,想知道大小直接size就可以了。

2.我們加一個輔助類Cat
Cat.java:
package cn.edu.hpu.iterator;

public class Cat {
	private int id;


	public Cat(int id) {
		super();
		this.id = id;
	}
	
	public int getId() {
		return id;
	}
	
	public void setId(int id) {
		this.id = id;
	}
}

以後新增元素就新增這個就可以了,因為可以通過id值知道添加了哪些資料
如測試:
package cn.edu.hpu.iterator;

import cn.edu.hpu.iterator.ArrayList;

public class ArrayListTest {
	public static void main(String[] args) {
		ArrayList al=new ArrayList();
		for(int i=0;i<20;i++){
			al.add(new Cat(i));
		}
		System.out.println(al.size());
	}
}


3.OK,現在我膩歪了,不想用ArrayList容器了,我想用其它容器去裝Cat,這個新的容器我們底層不用陣列實現了,我們用連結串列來實現。(需要你先了解資料結構中的連結串列概念)
建立一個新容器類LinkedList,一個節點類Node
LinkedList.java:
package cn.edu.hpu.iterator;

public class LinkedList {
	Node head=null;//頭節點(以後的元素通過next得到)
	Node tail=null;//尾節點
	int size=0;
	public void add(Object o){
		Node n=new Node(o,null);
		if(head==null){
			head=n;
			tail=n;
		}
		tail.setNext(n);
		tail=n;
		size++;
		
	}
	
	public int size(){
		return size;
	}
}


Node.java:
package cn.edu.hpu.iterator;

public class Node {
	private Object data;
	private Node next;
	
	
	public Node(Object data, Node next) {
		super();
		this.data = data;
		this.next = next;
	}
	
	public Object getData() {
		return data;
	}
	
	public void setData(Object data) {
		this.data = data;
	}
	
	public Node getNext() {
		return next;
	}
	
	public void setNext(Node next) {
		this.next = next;
	}
	
}

測試:
package cn.edu.hpu.iterator;

import cn.edu.hpu.iterator.LinkedList;

public class LinkedListTest {
	public static void main(String[] args) {
		LinkedList ll=new LinkedList();
		for(int i=0;i<20;i++){
			ll.add(new Cat(i));
		}
		System.out.println(ll.size());
	}
}
測試結果:20


我們實現了兩個內部原理完全不同的容器,那麼下面我們來看:

4.考慮容器的可替換性
什麼叫做可替換性?考慮我們使用容器的客戶端類,想使這些容器可以任意的替換,其它程式碼不用改變,這就需要我們把所有容器的對外提供的方法統一起來。
解決方案:
我們寫一個統一的介面Collection,定義一些統一的規範,讓所有容器去實現這個介面。
Collection.java:
package cn.edu.hpu.iterator;

public interface Collection {
	void add(Object o);
	int size();
}

ArrayList.java:
package cn.edu.hpu.iterator;

public class ArrayList implements Collection{
	
	Object[] objects=new Object[10]; 
	int index=0;
	public void add(Object o){
		if(index==objects.length){
			
			Object[] newObjects=new Object[objects.length*2];
			
			System.arraycopy(objects, 0, newObjects, 0, objects.length);
			objects=newObjects;
		}
		objects[index]=o;
		index++;
	}
	
	public int size(){
		return index;
	}
}

LinkedList.java:
package cn.edu.hpu.iterator;

public class LinkedList implements Collection{
	Node head=null;
	Node tail=null;
	int size=0;
	public void add(Object o){
		Node n=new Node(o,null);
		if(head==null){
			head=n;
			tail=n;
		}
		tail.setNext(n);
		tail=n;
		size++;
		
	}
	
	public int size(){
		return size;
	}
}

這樣我們就用Collection介面來實現對不同容器對外方法的統一。
在我們測試的時候就可以使用這種方式來實現容器:
Collection c=new ArrayList();
我對ArrayList不滿意,就可以改成
Collection c=new LinkedList();

小結:針對介面程式設計就不需要考慮具體的實現類是什麼,所以你的程式會更加靈活,要替換其它實現的時候,只需換一個位置(當然如果你寫在配置檔案上,你連程式碼都不用改)。

5.接下來重點來了,我們的容器需要一個非常非常重要的功能-------遍歷
我想遍歷容器中的所有元素,如何去做?
當然可以這樣來寫:
package cn.edu.hpu.iterator;

import cn.edu.hpu.iterator.ArrayList;
import cn.edu.hpu.iterator.Collection;

public class ArrayListTest {
	public static void main(String[] args) {
		Collection c=new ArrayList();
		for(int i=0;i<20;i++){
			c.add(new Cat(i));
		}
		System.out.println(c.size());
		
		ArrayList al=(ArrayList)c;
		for(int i=0;i<al.index;i++){
			Cat cat=(Cat)al.objects[i];
			System.out.print(cat.getId()+" ");
		}
	}
}
結果:
20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 

但是,當我們將Collection的實現改為LinkedList的時候下面的遍歷方法就不行了。
這個時候就需要一種統一的遍歷方式。

每一種容器都應該有它自己的一種遍歷方式,我們要想方設法的給他們統一起來,但是每種容器的遍歷器的實現又不一樣,所以統一遍歷方式只能用一種共同的實現方式,介面或抽象類的方式。

所以我們這麼寫:
定義一個介面Iterator:
package cn.edu.hpu.iterator;

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

其中有兩個未實現的方法:next和hasNext。
我要求任何一個容器都必須給我一個實現了這個介面的類的物件,
具體的實現交給具體的容器自己去實現。實現完了之後,我肯定知道,
這個物件就實現這兩個方法了,有了這兩個方法,我就可以實現對這
個容器的遍歷了。

我們在Collection中加入返回Iterator的未實現方法
package cn.edu.hpu.iterator;

public interface Collection {
	void add(Object o);
	int size();
	Iterator iterator();
}

ArrayList實現了iterator()這個方法
public Iterator iterator(){
		return null;
	}

測試:
package cn.edu.hpu.iterator;

import cn.edu.hpu.iterator.ArrayList;
import cn.edu.hpu.iterator.Collection;
import cn.edu.hpu.iterator.Iterator;

public class ArrayListTest {
	public static void main(String[] args) {
		Collection c=new ArrayList();
		for(int i=0;i<20;i++){
			c.add(new Cat(i));
		}
		System.out.println(c.size());
		
		/*之前的方法
                ArrayList al=(ArrayList)c;
		for(int i=0;i<al.index;i++){
			Cat cat=(Cat)al.objects[i];
			System.out.print(cat.getId()+" ");
		}*/

                //使用統一遍歷器的遍歷方法
                ArrayList al=(ArrayList)c;
		Iterator it=c.iterator();
		while(it.hasNext()){
			Object o=it.next();
			Cat cat=(Cat)o;
			System.out.print(cat.getId()+" ");
		}
	}
}
這樣我們怎麼改變Collection的實現類,都不影響遍歷方式。

6.當然我沒還沒有寫Iterator的實現,我們來寫實現:
我們在ArrayList中寫一個內部類ArrayListIterator,實現Iterator介面:
package cn.edu.hpu.iterator;

public class ArrayList implements Collection{

	Object[] objects=new Object[10]; 
	int index=0;
	
	public void add(Object o){
		if(index==objects.length){
			
			Object[] newObjects=new Object[objects.length*2];
	
			System.arraycopy(objects, 0, newObjects, 0, objects.length);
			objects=newObjects;
		}
		objects[index]=o;
		index++;
	}
	
	public int size(){
		return index;
	}
	
	public Iterator iterator(){
		return new ArrayListIterator();
	}
	
	private class ArrayListIterator implements Iterator{


		private int currentIndex=0;
		
		@Override
		public boolean hasNext() {
			if(currentIndex>index) return false;
			else return true;
		}

		@Override
		public Object next() {
			Object o=objects[currentIndex];
			currentIndex++;
			return o;
		}
		
	}
}

測試:
package cn.edu.hpu.iterator;

import cn.edu.hpu.iterator.ArrayList;
import cn.edu.hpu.iterator.Collection;
import cn.edu.hpu.iterator.Iterator;


public class ArrayListTest {
	public static void main(String[] args) {
		Collection c=new ArrayList();
		for(int i=0;i<20;i++){
			c.add(new Cat(i));
		}
		System.out.println(c.size());


                //使用統一遍歷器的遍歷方法
		ArrayList al=(ArrayList)c;
		Iterator it=c.iterator();
		while(it.hasNext()){
			Object o=it.next();
			Cat cat=(Cat)o;
			System.out.print(cat.getId()+" ");
		}
	}
}
測試結果:
20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 

在我們的JavaAPI中的ArrayList也實現了Java的Collection介面,同樣提供了一個方法,
叫iterator(),這個方法返回了一個實現了Iterator介面的物件。

現在大家明白iterator()方法到底是幹什麼用的,它返回的介面又是什麼意思了吧?

作業:
這個LinkedList也需要寫一個iterator方法,返回一個實現了Iterator的物件。該如何寫?

這是我自己寫的:

感謝馬士兵老師的"設計模式"視訊提供的幫助