デザインパターン入門 | Iterator(イテレータ)パターン

プログラミングには、必ずと言っていいほど登場するのが「繰り返し処理」ですが、1つ1つ次の要素を取り出して処理を作る際に利用できるデザインパターンが「Iterator(イテレータ)パターン」です。

自分でイテレータを作らずとも既にプログラム言語の仕様として用意されているものもあり、一から機能を作らなくてもすぐに利用できるプログラム言語もあります。

それでは、「Iterator(イテレータ)パターン」の仕組みや使い方について見ていきましょう。

繰り返し処理が必要なケースとプログラムの書き方

あなたは、いくつかの駐車場を所有している地主だと仮定します。

1つの駐車場には複数の契約者がいて、それらをプログラムで管理したいというケースについて考えてみましょう。

まず、駐車場の「契約者」が必要ですので、Java言語で「「契約者クラス」を作ります。

package IteratorSample;

public class Contractor {
    private String name;
    private String carType;

    public Contractor(String name, String carType){
        this.name = name;
        this.carType = carType;
    }

    public String getName() {
        return name;
    }
    public String getCarType() {
        return carType;
    }
    public void setCarType(String carType) {
        this.carType = carType;
    }
    
    @Override
    public String toString() {
        return "契約者名:" + this.name + " 車種タイプ:" + this.carType;
    }
}

車の乗り換えなどで、車のタイプは変わる可能性がありますが、同じ契約で契約者の名前は変わらないと仮定し、Getterのみを作成しています。

次に必要になるのは「駐車場を表すオブジェクト」なので、「駐車場クラス」を作ります。

package IteratorSample;

public class ParkingLot implements Aggregate {
    private String name;               // 駐車場名
    private String address;            // 住所
    private int maxNumOfCars;       // 最大駐車可能台数
    private int currentNumOfCars;   // 現在の契約者台数
    private Contractor[] contractors;   // 契約者の情報

    // コンストラクタ
    public ParkingLot(String name, String address, int maxNumberOfCars){
        this.name = name;
        this.address = address;
        this.maxNumOfCars = maxNumberOfCars;
        this.currentNumOfCars= 0;
        this.contractors = new Contractor[maxNumberOfCars];
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public Contractor getCurrentContract(){
        return this.contractors[currentNumOfCars];
    }

    public Contractor getContract(int num) {
        return contractors[num];
    }

    public int getNumOfConstract(){
        return currentNumOfCars;
    }

    @Override
    public Iterator iterator() {
        return new ParkingLotIterator(this);
    }

    public void appendContractor(Contractor contractor) {
        this.contractors[currentNumOfCars] = contractor;
        currentNumOfCars++;
    }
}

駐車場名は変わることがありますが、住所や駐車可能台数は変わることは無いと仮定し、Getterのみを作成しています。

この2つのクラスの役割や下図のようなイメージになります。

クラス

次に、「Aggregate(アグリゲート)」インターフェースを作ります。

「Aggregate(アグリゲート)」は「集まり・集計・集約」といった「何かの集まり」という意味を持つ英単語です。

今回は、駐車場の「契約者」が「集まる」というイメージになりますが、「Aggregate(アグリゲート)」インターフェースは、「iterator抽象メソッド」を一つ定義しているだけです。

package IteratorSample;

public interface Aggregate {
    public abstract Iterator iterator();
}

「Aggregate(アグリゲート)」インターフェースを実装する先のクラスは「ParkingLot」クラスです。

そのため、「ParkingLot」クラスに「iterator」メソッドを追加する必要があります。

「iterator」メソッドでは「ParkingLotIterator」クラスのインスタントを生成しますが、まだ「ParkingLotIterator」クラスは作成していないため後ほど作成していきます。

package IteratorSample;

public class ParkingLot implements Aggregate {
    private String name;               // 駐車場名
    private String address;            // 住所
    private int maxNumOfCars;       // 最大駐車可能台数
    private int currentNumOfCars;   // 現在の契約者台数
    private Contractor[] contractors;   // 契約者の情報

    // コンストラクタ
    public ParkingLot(String name, String address, int maxNumberOfCars){
        this.name = name;
        this.address = address;
        this.maxNumOfCars = maxNumberOfCars;
        this.currentNumOfCars= 0;
        this.contractors = new Contractor[maxNumberOfCars];
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public Contractor getCurrentContract(){
        return this.contractors[currentNumOfCars];
    }

    public Contractor getContract(int num) {
        return contractors[num];
    }

    public int getNumOfConstract(){
        return currentNumOfCars;
    }

    @Override
    public Iterator iterator() {
        return new ParkingLotIterator(this);
    }

    public void appendContractor(Contractor contractor) {
        this.contractors[currentNumOfCars] = contractor;
        currentNumOfCars++;
    }
}

これで、現在までに1つのインターフェースと2つのクラスが出来上がりました。

クラス・インターフェース

ここからさらに1つのインタフェースと1つのクラスを作成していきます。

まず「Iterator」インターフェースを作成します。

package IteratorSample;

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

このインターフェースは「hasNext」「next」の2つの抽象メソッドを持っています。

「Iterator」インターフェースを実装する先のクラスは次に新しく作成する「ParkingLotIterator」クラスです。

package IteratorSample;

public class ParkingLotIterator implements Iterator {
    private ParkingLot parkingLot;
    private int currentNum;

    public ParkingLotIterator(ParkingLot parkingLot) {
        super();
        this.parkingLot = parkingLot;
        this.currentNum = 0;
    }

    @Override
    public boolean hasNext() {
        if ( currentNum < parkingLot.getNumOfConstract() ){
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Object next() {
        Contractor contractor = parkingLot.getContract(currentNum);
        currentNum++;
        return contractor;
    }
}

「Iterator」インターフェースと「ParkingLotIterator」クラスの関係は下図のようになります。

クラス・インターフェース2

これでようやくイテレーション(繰り返し処理を実行する準備が整いました。

処理を実行するMainクラスは下図のようになります。

package IteratorSample;

public class Main {
    public static void main(String[] args) {
        ParkingLot pl = new ParkingLot("西山第一駐車場", "東京都国立市", 10);
        pl.appendContractor(new Contractor("田山 洋子","sedan"));
        pl.appendContractor(new Contractor("時田 卓","wagon"));
        pl.appendContractor(new Contractor("寺西 和也","wagon"));
        pl.appendContractor(new Contractor("池田 薫","sedan"));
        pl.appendContractor(new Contractor("小西 楓","suv"));
        pl.appendContractor(new Contractor("山田 健太郎","truck"));
        pl.appendContractor(new Contractor("橋本 慎二","sedan"));
        Iterator plit = pl.iterator();
        while ( plit.hasNext() == true) {
            Contractor contractor = (Contractor)plit.next();
            System.out.println(contractor);
        }
    }
}

今回は「println」メソッドでConstractorインスタンスの内容を出力をしているだけですが、出力結果は下記のようになります。

契約者名:田山 洋子 車種タイプ:sedan
契約者名:時田 卓 車種タイプ:wagon
契約者名:寺西 和也 車種タイプ:wagon
契約者名:池田 薫 車種タイプ:sedan
契約者名:小西 楓 車種タイプ:suv
契約者名:山田 健太郎 車種タイプ:truck
契約者名:橋本 慎二 車種タイプ:sedan

5個もクラスとインターフェースが出てきて、複雑に感じられた人もいるのではないかと思います。

正直「使いづらいな~」と感じられたのではないでしょうか。

Javaであれば「ava.lang.Iterableインターフェイス」があらかじめ用意されているため、一からインターフェースを作らなくても「java.lang.Iterableインターフェイス」「java.util.Iteratorインターフェイス」を実装すれば、クラスに実装することで反復処理を行うことができます。

詳しい実装方法は、

→「拡張for文の真の実力を知り、反復処理を使いこなせ」

をご覧ください。

HOMEへ