LoginSignup
0
0

【Java】ポケモンで学ぶデザインパターン Iterator編

Posted at

はじめに

この記事はJava Advent Calendar 2023の12日目の記事になります。

本記事は、デザインパターンのIteratorパターンについて取り上げ、Javaで実装してみた事例の紹介になります。対象は、デザインパターンについてあまり知らない方向けで、実際に業務で設計する際に意識してもらえるきっかけになればいいと思います。

デザインパターンについて

デザインパターンは、GoF(Gang of four)によって提唱された、ソフトウェア設計の問題や課題に対する解決策を提供するための再利用可能な設計のテンプレートです。ソフトウェア開発において、特定の問題に遭遇した際にデザインパターンを適用することで、効率的で柔軟なソリューションを提供することができます。デザインパターンはいくつかの種類があり、その中でも今回はIteratorパターンについて取り上げます。

Iteratorパターンの概要

Iteratorパターンとは、集約されたオブジェクトを列挙するのに使われるデザインパターンです。配列やオブジェクトなどの要素をfor文等で列挙するときに役立ちます。とはいっても、説明やクラス図を見ただけでは具体的にどういう場面で使うか想像が難しいと思います。
本記事では、ポケモンの世界を例に挙げてこのIteratorパターンについて学んでいきましょう。

Iteratorパターンなしでの実装

まずは、デザインパターンを使用せずにこのような要件をJavaを用いて実装してみようと思います。

  • ポケモントレーナーの手持ちのポケモンを全て列挙する
  • ポケモンは名前(name)とレベル(level)を要素として持つ

まずは、このようなポケモンのモデルクラスを実装します

Pokemon.java

public class Pokemon {
    
    private String name;
    private int level;

    public Pokemon(String name, int level) {
        this.name = name;
        this.level = level;
    }

    public String getName() { return name; }

    public int getLevel() { return level; }

}

各要素のgetterとコンストラクタだけ定義しておきます。
次に、Pokemonクラスを要素に持つ、PokemonListクラスを作成します。

PokemonList.java

public class PokemonList {

    private Pokemon[] pokemons;
    private int last = 0;

    public PokemonList(int size) {
        this.pokemons = new Pokemon[size];
    }

    public void add(Pokemon pokemon) {
        pokemons[last] = pokemon;
        last++;
    }

    public Pokemon get(int index) {
        return pokemons[index];
    }

    public int getLastNum() {
        return last;
    }

}

PokemonListコンストラクタ作成時に、配列の要素の最大値を設定するようにします。
ポケモンの追加は(add)で、最後の要素の添え字を(getLastNum)で取り出せるようにします。
また、配列の要素の i 番目のポケモンを取り出せるようにします(get)
続いて、ポケモントレーナーの手持ちのクラスを作成します。

TrainersHand.java


public class TrainersHand {

    private PokemonList pokemonList;

    public TrainersHand(int size) {
        this.pokemonList = new PokemonList(size);
    }

    public void addPokemon(Pokemon pokemon) {
        pokemonList.add(pokemon);
    }

    public PokemonList getPokemonList() {
        return pokemonList;
    }

}

TrainersHandクラスは、先ほど定義したPokemonListクラスを要素として持ちます。TrainersHandクラスのインスタンスを作成するときに、手持ちの数を確定させます。ポケモントレーナーの手持ちは本来は最大6匹なので、必要に応じてエラー処理を入れてください。
(本記事では省略しています)
後は、ポケモンの追加、取得ができるようになっています。

そしてMainクラスの実装です。トレーナーのたんぱんこぞうはコラッタ(Lv.10)、オニスズメ(Lv.9)、ポッポ(Lv.8)を手持ちにいるとします。たんぱんこぞうの手持ちポケモンをfor文を使って列挙してみましょう。

image.png

(わりと序盤ってこんな手持ちしてませんかね...)

Main.java

import java.util.Iterator;

public class Main {
    public static void main(String[] args) {

         TrainersHand trainersHand = new TrainersHand(3);
         trainersHand.addPokemon(new Pokemon("コラッタ", 10));
         trainersHand.addPokemon(new Pokemon("オニスズメ", 9));
         trainersHand.addPokemon(new Pokemon("ポッポ", 8));
         PokemonList pokemonList = trainersHand.getPokemonList();
    
        for(int i=0; i< pokemonList.getLastNum(); i++){
            Pokemon pokemon = pokemonList.get(i);
            System.out.println(pokemon.getName() + " Lv."+ pokemon.getLevel());
        }
    }

}


実行結果

コラッタ Lv.10
オニスズメ Lv.9
ポッポ Lv.8

このような形で要素の数だけfor文のループを回し、i 番目の要素を取り出すことでリストの要素を出得することが出来ますね。

この要素を逆順に表示したい場合はfor文をこのように書きます。Mainのfor文のロジックに手を加える必要がありますね。

for(int i= pokemonList.getLastNum()-1; i >= 0; i--){
    Pokemon pokemon = pokemonList.get(i);
    System.out.println(pokemon.getName() + " Lv."+ pokemon.getLevel());
}

実行結果

ポッポ Lv.8
オニスズメ Lv.9
コラッタ Lv.10

Iteratorパターンによる実装

次に、上記の実装をIteratorパターンを用いてみましょう。
以下は、Iteratorパターンのクラス図です。

クラス図

image.png

  • Iterator
    要素を順番に数え上げるインターフェース(API)です。次の要素が存在するか(hasNext)と、次の要素を得る(next)を定義します。

  • Aggregate
    Iteratorを作りだすインターフェース(API)を定めます。要素を順番にスキャンする役割であるiteratorメソッドを定義します。

  • ConcreteAggregate
    Iteratorが定めたhasNext、nextを実装するクラスです。

  • ConcreteIterator
    Aggregateが定めたiteratorを実装するクラスです

インターフェースの実装

Iterator.java

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

Aggregate.java

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

この2つのインターフェースは、それぞれ実装するためのメソッドを定義しておきます。

続いて、ConcreteIteratorの実装になります。

PokemonListIterator.java


public class PokemonListIterator implements Iterator{

    private PokemonList pokemonList;
    private int index;

    public PokemonListIterator(PokemonList pokemonList) {
        this.pokemonList = pokemonList;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
         if(index < pokemonList.getLastNum())
             return true;
         else{
             return false;
         }
    }

    @Override
    public Object next() {   
        Pokemon pokemon = pokemonList.get(index);
        index++;
        return pokemon;
    }
    
}

hasNext()では、PokemonListの要素が最後かどうか判定します。
next()は、次の要素のPokemonを返すようにします。
PokemonListは、Aggregateインターフェースをimplementし、iteratorメソッドを実装します。

PokemonList.java

public class PokemonList implements Aggregate{
    private Pokemon[] pokemons;
    private int last = 0;

    public PokemonList(int size) {
        this.pokemons = new Pokemon[size];
    }

    public void add(Pokemon pokemon) {
        pokemons[last] = pokemon;
        last++;
    }

    public Pokemon get(int index) {
        return pokemons[index];
    }

    public int getLastNum() {
        return last;
    }
    
    @Override
    public Iterator iterator(){
        return new PokemonListIterator(this);
    }

}

Mainクラスは、Iteratorを取得し、for文の実装を以下のようにしてください。
hasNext()で次の要素があるか判定します。次の要素がなくなるまでwhileループを回し、next()で、次の要素を取得します。

Main.java

public class Main{
     public static void main(String[] args) {
        Iterator it =  (Iterator) pokemonList.iterator();
        while(it.hasNext()){
            Pokemon pokemon = (Pokemon) it.next();
            System.out.println(pokemon.getName() + " Lv."+ pokemon.getLevel());
        }
    }
}

実行結果

コラッタ Lv.10
オニスズメ Lv.9
ポッポ Lv.8

実行結果は、Iteratorを使う前の実装と変わらないですね。
逆順の表示もIteratorでやってみましょう。

PokemonListIterator.java

public class PokemonListIterator implements Iterator{

    private PokemonList pokemonList;
    private int index;

    public PokemonListIterator(PokemonList pokemonList) {
        this.pokemonList = pokemonList;
        this.index = pokemonList.getLastNum()-1;
    }

    @Override
    public boolean hasNext() {
        if(index < pokemonList.getLastNum() && index >= 0)
            return true;
        else{
            return false;
        }
    }

    @Override
    public Object next() {
        Pokemon pokemon = pokemonList.get(index);
        index--;
        return pokemon;
    }

}

このようにhasNext()とnext()のロジックを変更してあげます。Mainのロジックは変えません。

実行結果

ポッポ Lv.8
オニスズメ Lv.9
コラッタ Lv.10

Mainのwhileループはそのままで実行結果はちゃんと逆順になっていますね。

クラスの型が変更になった場合

次に、Iteratorで回すリストが、ArrayListになった時のパターンとその変更方法についても検討してみます。

変更前のリストの型

Pokemon[] pokemons

変更後のリストの型

ArrayList<Pokemon> pokemons

実装はこのようになります。

PokemonList.java

public class PokemonList implements Aggregate{

    private ArrayList<Pokemon> pokemons;
    private int last = 0;

    public PokemonList(int size) {
        this.pokemons = new ArrayList<Pokemon>(size);
    }

    public void add(Pokemon pokemon) {
        pokemons.add(pokemon);
        last++;
    }

    public  Pokemon get(int index) {
        return pokemons.get(index);
    }

    public int getLastNum() {
        return last;
    }

    public Iterator iterator(){
        return new PokemonListIterator(this);
    }

}

型の変更に合わしてあげるだけでよくなりましたね。
Mainクラスは、先ほど同様に変更を加えなくて大丈夫です。Iteratorを使わない場合は、リストの型が違うのでfor文を書き換えないといけないですね。

まとめ

今回は、Iteratorパターンを取り上げ、使わない場合と使った場合の実装の違いについて理解していただけたと思います。Iteratorパターンを使うことで、配列であろうとArrayListであろうとMainの実装とは切り離せます。また、一度作成したIteratorは再利用性が高く、Mainでの数え上げるメソッドを変えずIteratorの部分だけ変更することで動作します。このような設計や考え方は大規模な開発においてとても重要です。

また、本記事では、Iteratorパターンの理解に重きを置くために、あえて拡張for文を用いませんでしたが、Javaの拡張for文って実はIteratorパターンを使って実装されていたりします。ただ、内部でどのような動きをしているのか分かっている状態で使うとより理解が深まるかもしれませんね。

参考

オブジェクト指向における再利用のためのデザインパターン

Java言語で学ぶデザインパターン入門

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0