はじめに
GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。
Iteratorパターン
Iteratorとは
Iteratorとは英単語で日本語では「反復子」を意味します。
特にプログラミングにおいては、集合体に対して順次にアクセスを行う際に、「指し示すもの」を抽象化し、一般化したものを意味します。
この説明だけではイメージが掴みにくいと思うので、もう少し具体的な例をあげます。
以下のような配列array
の要素を全て表示するような繰り返し文を書いたことがある人は多いのではないでしょうか。
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
ここで変数i
は配列array
の添え字となり、配列の持つ要素に順次にアクセスを行っています。
このように「繰り返して全体を指し示し、走査を行う」ものがIteratorです。
登場人物
Itaratorパターンで使用するのは以下のクラス図に登場するクラスとインターフェースです。
インターフェース
-
Aggreate
数え上げを行う対象の「集合体」を表します。
メソッドとしてはIterator()
のみを持ちます。
これはIterator生成するために使用します。
public interface Aggregate {
public abstract Iterator iterator();
}
-
Iterator
集合の要素の「数え上げを行うもの」を表します。
メソッドとしてはhasNext()
とnext()
の2つを持ちます。
hasNext()
は次の要素を持つかどうかを確認するために使用します。
next()
は次の要素があった際に次の要素を取得するために使用します。
public interface Iterator {
public abstract boolean hasNext();
public abstract Object next();
}
実装クラス
インターフェースを実装し、具体的な処理を記述します。
具体例については後述いたします。
- ConcreateAggreate
- ConcreateIterator
具体例
具体例として、「従業員をまとめる従業員リスト」と「従業員リストのIterator」をもとに説明します。
実装クラス
-
Employeeクラス
Employee(従業員)クラスは集合体の要素を表します。
名前と従業員コードを持ちます。
public class Employee {
private String name;
private String employeeCode;
public Employee(String name, String employeeCode) {
this.name = name;
this.employeeCode = employeeCode;
}
public String getName() {
return name;
}
public String getEmployeeCode() {
return employeeCode;
}
}
-
EmployeeListクラス
EmployeeList(従業員リスト)クラスは従業員の集合体を表します。
従業員の配列と添え字を持ちます。
iterator()
において自身を引数としてEmployeeListIterator
を生成して返しています。
public class EmployeeList implements Aggregate {
private Employee[] employees;
private int last = 0;
public EmployeeList(int maxsize) {
this.employees = new Employee[maxsize];
}
public Employee getEmployeeAt(int index) {
return employees[index];
}
public void appendEmployee(Employee employee) {
this.employees[last] = employee;
last++;
}
public int getLength() {
return last;
}
public Iterator iterator() {
return new EmployeeListIterator(this);
}
}
-
EmployeeListIteratorクラス
EmployeeListIterator(従業員リストIterator)クラスは従業員の集合体を数え上げるものを表します。
従業員リストと添え字を持ちます。
hasNext()
で従業員リストが次の要素を持つかを確認し、next()
で従業員リストの次の要素(従業員)を返却しています。
順次にアクセスを行う方法は様々ですが、今回は単純に前から順次でアクセスする実装を行います。
public class EmployeeListIterator implements Iterator {
private EmployeeList employeeList;
private int index;
public EmployeeListIterator(EmployeeList employeeList) {
this.employeeList = employeeList;
this.index = 0;
}
public boolean hasNext() {
if (index < employeeList.getLength()) {
return true;
} else {
return false;
}
}
public Object next() {
Employee employee = employeeList.getEmployeeAt(index);
index++;
return employee;
}
}
実行クラス
-
Mainクラス
従業員リストに従業員を追加し、追加した従業員の名前と従業員コードを従業員リストから出力しています。
public class Main {
public static void main(String[] args) {
EmployeeList employeeList = new EmployeeList(4);
employeeList.appendEmployee(new Employee("Tarou_Tanaka", "C001"));
employeeList.appendEmployee(new Employee("Hanako_Yamada", "C002"));
employeeList.appendEmployee(new Employee("Yuuya_Suzuki", "C003"));
employeeList.appendEmployee(new Employee("Kanako_Satou", "C004"));
Iterator it = employeeList.iterator();
while (it.hasNext()) {
Employee employee = (Employee) it.next();
System.out.println(employee.getName() + ":" + employee.getEmployeeCode());
}
}
}
実行結果
Main.class
を実行した結果は以下になります。
employeeList
に追加した順にきちんと出力されていることが確認できます。
Tarou_Tanaka:C001
Hanako_Yamada:C002
Yuuya_Suzuki:C003
Kanako_Satou:C004
メリット
Iteratorを使用することで数え上げを行う際に集合体がどのような集合体であるか(配列、ArrayList、Vectorなど)、どのような実装が行われているかを意識する必要がなくなります。
以下で集合体を配列からArrayListに変更したEmployeeList.class
を定義します。
この時、実行クラスのMain.class
を修正する必要はなく、実行結果は配列の場合と同様です。
import java.util.ArrayList;
public class EmployeeList implements Aggregate {
private ArrayList<Employee> employees;
public EmployeeList(int initialsize) {
this.employees = new ArrayList<>(initialsize);
}
public Employee getEmployeeAt(int index) {
return (Employee) employees.get(index);
}
public void appendEmployee(Employee employee) {
employees.add(employee);
}
public int getLength() {
return employees.size();
}
public Iterator iterator() {
return new EmployeeListIterator(this);
}
}
Tarou_Tanaka:C001
Hanako_Yamada:C002
Yuuya_Suzuki:C003
Kanako_Satou:C004
追記 (2018/10/31)
Iteratorには前から順次でアクセスする以外にも様々なアクセス方法があります。(逆順、1つ飛ばしなど)
今回はArrayListで逆順でアクセスするIteratorを追加いたしました。
差分のあるクラスのみ掲載しますので、その他のクラスについてはまとめで確認してください。
インタフェース
-
Aggregate
逆順にアクセスするIteratorを生成するreverseIterator()
を追加しています。
public interface Aggregate {
public abstract Iterator iterator();
public abstract Iterator reverseIterator();
}
実装クラス
-
EmployeeListクラス
reverseIterator()
を追加しています。
reverseIterator()
において自身を引数としてEmployeeListReverseIterator
を生成して返しています。
import java.util.ArrayList;
public class EmployeeList implements Aggregate {
private ArrayList<Employee> employees;
public EmployeeList(int initialsize) {
this.employees = new ArrayList<>(initialsize);
}
public Employee getEmployeeAt(int index) {
return (Employee) employees.get(index);
}
public void appendEmployee(Employee employee) {
employees.add(employee);
}
public int getLength() {
return employees.size();
}
public Iterator iterator() {
return new EmployeeListIterator(this);
}
public Iterator reverseIterator() {
return new EmployeeListReverseIterator(this);
}
}
-
EmployeeListReverseIteratorクラス
新規に作成するクラスですが、順次アクセスを行うEmployeeListIterator.class
と同様にhasNext()
とnext()
を実装しています。
コンストラクタでemployeeList
の要素数から1を引いた数字をindex
に持ちます。
これにより仮に要素数が5のリストからは4を取得することになります。
hasNext()
ではArrayListの要素の数だけ逆順からアクセスを行います。
注意すべき点としてはループの条件でindex
が0未満にならないようにする必要があることです。
この条件がなければnext()
でemployeeList[-1]にアクセスしようとしてしまいArrayIndexOutOfBoundsException
が発生します。
public class EmployeeListReverseIterator implements Iterator {
private EmployeeList employeeList;
private int index;
public EmployeeListReverseIterator(EmployeeList employeeList) {
this.employeeList = employeeList;
this.index = employeeList.getLength() - 1;
}
public boolean hasNext() {
if (index < employeeList.getLength() && index >= 0) {
return true;
} else {
return false;
}
}
public Object next() {
Employee employee = employeeList.getEmployeeAt(index);
index--;
return employee;
}
}
実行クラス
-
Mainクラス
EmployeeList
にEmployee
を追加し、順次で出力をした後、逆順で出力を行っています。
public class Main {
public static void main(String[] args) {
EmployeeList employeeList = new EmployeeList(4);
employeeList.appendEmployee(new Employee("Tarou_Tanaka", "C001"));
employeeList.appendEmployee(new Employee("Hanako_Yamada", "C002"));
employeeList.appendEmployee(new Employee("Yuuya_Suzuki", "C003"));
employeeList.appendEmployee(new Employee("Kanako_Satou", "C004"));
Iterator it = employeeList.iterator();
System.out.println("------- Order -------");
while (it.hasNext()) {
Employee employee = (Employee) it.next();
System.out.println(employee.getName() + ":" + employee.getEmployeeCode());
}
System.out.println("------- Reverse Order -------");
Iterator rit = employeeList.reverseIterator();
while (rit.hasNext()) {
Employee employee = (Employee) rit.next();
System.out.println(employee.getName() + ":" + employee.getEmployeeCode());
}
}
}
実行結果
Main.class
を実行した結果は以下になります。
employeeList
に追加した順と、追加したものと逆順で出力されていることが確認できます。
------- Order -------
Tarou_Tanaka:C001
Hanako_Yamada:C002
Yuuya_Suzuki:C003
Kanako_Satou:C004
------- Reverse Order -------
Kanako_Satou:C004
Yuuya_Suzuki:C003
Hanako_Yamada:C002
Tarou_Tanaka:C001
まとめ
集合体に順次にアクセスを行うIteratorパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。
また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。