うさぎ組

ソフトウェア開発、チームによる製品開発、アジャイル、ソフトウェアテスト

継承を使う理由をテンプレートメソッドパターンで説明してみる

[Java]継承を使う理由をテンプレートメソッドパターンで説明してみる。

継承の使いどころってよくわからないことがありますが、
自分はテンプレートメソッドパターンを知ったときに
「あぁ、なるほど」と納得できました。


ということで簡単にテンプレートメソッドパターンを説明。


例として「画像を表示する方法」として考えてみます。


デスクトップアプリでもWebアプリでもいいのですが、
表示する画像は様々なパターンが考えられます。

ユーザーがアップロードしたものであったり、
すでに用意してある画像であったり、
違うサイトから直リンクってこともありえます。

そうなると、画像の取得する方法や表示するサイズが異なるようになるので、
それぞれに実装が異なるようになります。


2パターンなら2パターン、3パターンなら3パターンをそれぞれ好きに実装することもできますが、
そうすると、そのクラスを使う側からすれば、
「アップロードした画像を取得するときは『A,B,C』って手順を踏むのにアプリで用意してある画像の場合は『D,B,F』って処理を踏まなきゃいけない」
ってことになる可能性があります。


どちらの処理にしても「目的の画像を取得したい」というのに処理手順が違うのは混乱しやすくなってしまいます。


そこでテンプレートメソッドパターンを活用します。
テンプレートメソッドパターンではおおまかな処理順をスーパークラスで決めて、
処理の詳細はサブクラスで実装します。


上の例をテンプレートメソッドパターンに当てはめると次のとおりにできます。


スーパークラス:画像を取得する
サブクラス1:セッションからアップロードした画像を取得する
サブクラス2:アプリで用意した(DBにパスを保存している)画像を取得する

たとえば以下のようになります。

/**
 * 画像を保持しておくModel
 */
public class Model {

    private Image img = new Image();

    public Image getImg() {
        return img;
    }

    public void setImg(Image img) {
        this.img = img;
    }
}

/**
 * 画像を生成するクラス
 */
public abstract class ImageManager {

    /**
     * imgに画像を設定する
     */
    public final void set(Image img){
        getPath();
        adjustSize();
        setImage(img);
    }

    /**
     * 画像パスを取得する。サブクラスで実装。
     */
    protected abstract void getPath();
    /**
     * 画像サイズを調整する。サブクラスで実装。
     */
    protected abstract void adjustSize();
    /**
     * getPath,adjudtSizeで取得した値を元にimgに画像を設定する。
     * サブクラスで実装。
     */
    protected abstract void setImage(Image img);

}

/**
 * 画像をセッションから生成するクラス
 */
public class ImageManagerSession extends ImageManager {

    @Override
    protected void getPath() {
        //セッションからユーザーがアップロードした
        //画像パスなどを取得する
    }

    @Override
    protected void adjustSize() {
        //任意のサイズに調整する
    }

    @Override
    protected void setImage(Image img) {
        //Imageオブジェクトにパスやサイズなどを設定する
    }
}

/**
 * 画像をDBから生成するクラス
 */
public class ImageManagerDB extends ImageManager{

    @Override
    protected void getPath() {
        //DBに接続して画像パスを取得する
    }

    @Override
    protected void adjustSize() {
        //任意のサイズに調整する
    }

    @Override
    protected void setImage(Image img) {
        //Imageオブジェクトにパスやサイズなどを設定する。
    }
}


で、これらを使って実際に画像を取得するときは

        /**
         * DBから画像を取得して設定する場合
         */
        ImageManager manager = new ImageManagerDB();
        Image img = new Image();
        manager.set(img);
        Model model = new Model();
        model.setImg(img);

 
       /**
         * セッションから画像を取得し設定する場合
         */
        ImageManager manager = new ImageManagerSession();
        Image img = new Image();
        manager.set(img);
        Model model = new Model();
        model.setImg(img);

こんな感じでmanager変数に代入するインスタンスを入れ替えるだけで
インスタンス化するImageMangerのサブクラスを入れ替えるだけで)
どこから画像を取得するかを変えられます。


ある程度のアルゴリズムを決めてしまって、
各処理の実装詳細はサブクラスに任せることで実装の一貫性を保っています。
そうするとサブクラスでどこに何が実装されているかがわかりやすくなりますし、
クライアントからしても使うときにはスーパークラスが用意している
インターフェース(メソッド。ここでいうset(Image img)メソッド)を知っていればいいので、
サブクラスが多くなっても使いやすさはかわりません。


これが思い思いに実装してしまうと、
ImageManagerXXXではhogeAAA()で○○○をやっていて、hogeBBB()で□□□をやっていて、、、実際に使うにはhogeEEE()を使う。
ImageManagerYYYではpiyoAAA()で□□□をやっていて、piyoBBB()で△△△をやっていて、、、実際に使うにはpiyoEEE()と使う。
なんてなってしまう可能性があって、
ImageManagerXXXを使うときとImageManagerYYYを使うときにまるで違うものを読むことになって面倒です。
なにより、修正しづらくなるというのもあります。


ということで、テンプレートメソッドパターンのように継承を使うとソースをきれいに保って、
使うときも楽に使えるようになります。



※サブクラスの命名方法って何がいいんですかねぇ。