図1●立ち食いソバ屋に見るCommandパターン
図1●立ち食いソバ屋に見るCommandパターン
[画像のクリックで拡大表示]

 OOPの概念を説明する際によく使われる、有名な例え話がいくつかあります。「クラスはクッキー型であり、生地からくり抜かれたクッキーがオブジェクトである」「多態性とは、犬に鳴けと命じると”ワン”と応え、猫に鳴けと命じると”ニャア”と応えることである」「自動車と船と飛行機は、乗り物に汎化できる」などです。きっと皆さんも、似たような例え話を聞いたことがあるでしょう。

 「例え話は、概念を十分に理解している人だけにわかるジョークのようなものだ。はじめて学ぶ人には、例え話を使わない方がいい」と言う人がいます。確かに、突拍子もない例え話をしたら、かえって理解の妨げになるでしょう。しかし、適切な例え話を交えた説明の方が、イメージが広がって聞きやすいのも事実です。例え話を使うべきか、それとも使わない方がいいのか...結論が出ないまま、CommandパターンとStrategyパターンの説明をさせていただきます。

【お役立ち度】★★★☆☆
●命令を1つのオブジェクトで表すCommandパターン

 突然ですが、立ち食いソバは、お好きですか? 私は、大好きです。学生時代に、駅前の立ち食いソバ屋でアルバイトをしていたぐらいです。駅に電車が着くと、一気に10人ぐらいのお客さんが店に入って来ます。「天ぷらソバ!」「きつねウドン!」「たぬきソバ!」...片っ端から注文が飛んできます。お客さんは、メニューを言うだけで目的の料理が食べられます。どのように作るかは、店員にお任せです。この様子は、Command(命令)パターンによく似ています。

 Commandパターンは、命令を表すオブジェクト(これを「コマンドオブジェクト」と呼ぶ)をメソッドの引数にするという工夫です。何が便利なのかと言うと、オブジェクトはデータと処理の集合体ですから、1つの引数だけで複数のデータとその処理方法を伝えられることです。ただし、ビックリするほどの工夫ではないので、評価は星3つとしました。

 立ち食いソバ屋では、お客さんが店員に注文することがメソッド呼び出しに相当します。これをプログラムに表したときに、setOrderという名前のメソッドになるとしましょう。オブジェクトを引数としない場合の「天ぷらソバ!」という注文は、setOrder(ソバ, 湯がく, 天ぷら, 乗せる, つゆ, 注ぐ、ねぎ, 入れる); のように複数の引数を指定したものとなります。お客さんがレシピを伝えているようなものです。そんな立ち食いソバ屋は、ないですね。データ(何を)と処理(どうする)を1つにまとめたオブジェクトを、メソッドの引数にするべきです。天ぷらソバ、きつねウドン、たぬきソバなど、個々のメニューがオブジェクトになります。お客さんは、オブジェクトを引数にしてsetOrder(天ぷらソバ); やsetOrder(きつねウドン); で注文できます(図1[拡大表示])。

 実際のプログラムでは、汎化のテクニックを使って、様々な種類のオブジェクトを一元管理できるようにしておきます。麺類を表すMenruiというインターフェイスを作っておいて、それを実装する形で、個々のメニューを表すTenpraSobaクラス、KitsuneUdonクラス、TanukiSobaクラスなどを定義します。setOrderメソッドの引数のデータ型をMenruiインターフェイスにすれば、それを実装しているすべてのメニューを指定できます。このあたりは、OOPのお約束みたいなものです。

 Commandパターンで注目すべきは、レシピはクラスを作る側、すなわち店員の側にあることです。お客さんが知っていなければならないのは、setOrder(Menrui m); という形式で注文することだけです。店員の側は、任意にメニューを増やすことも、レシピを変えることもできます。「私はがんばって料理を作る人(クラスを作る人)、あなたは楽して料理を食べる人(クラスを使う人)」という考え方も、毎度お馴染みOOPのお約束です。

【お役立ち度】★★★☆☆
●アルゴリズムを汎化して交換可能にするStrategyパターン

 Strategy(戦略)パターンは、プログラムの実行時にアルゴリズムを切り替えるための工夫です。このパターンのポイントは、クラスを使う人のコードを変更することなくアルゴリズムを変えられることです。先ほどの立ち食いソバ屋を例にすれば、同じ料理のレシピを何通りか用意してあって、状況に応じて切り替えられるようなメージです。

 事実、立ち食いソバ屋でアルバイトをしているときに、そういうことがあったのです。天ぷらは、お店で揚げる場合と、出来合いの天ぷらを使う場合がありました。天ぷらを上手に揚げられない店員もいます。そのときは、一斗缶に入った煎餅みたいな天ぷらを使っていました。味は落ちますが、見た目は何とかなります。やっぱり天ぷらは、揚げたてが美味しいですね。

 リスト1[拡大表示]は、Strategyパターンを使って、2つのアルゴリズム(天ぷらを得る方法)を切り替え可能にしたものです。これは、クラスを作る人が用意するコードです。getTenpra(天ぷらを得る)メソッドを持つTenpraRecipe(天ぷらのレシピ)インターフェイスがあり、それを実装するMakeTenpura(天ぷらを作る)クラスとBuyTenpura(天ぷらを買う)クラスがあります。これら2つのクラスで実装されているgetTenpraメソッドの処理内容が、2つのアルゴリズムというわけです(処理内容は省略する)。このようなクラスを「ストラテジクラス」と呼びます。

 店員を表すTeninクラスには、現在のアルゴリズム(レシピ)を表すオブジェクトを格納するcurrentReceipeフィールドがあります。このフィールドには、コンストラクタで初期のアルゴリズムを代入し、必要に応じてsetReceipeメソッドで別のアルゴリズムに切り替えます。現在のアルゴリズムで天ぷらを得るgetTenpra メソッドは、makeFoodメソッドの処理の中で呼び出されます。

リスト1●アルゴリズムを汎化するStrategyパターン

// 天ぷらのレシピを表すインターフェイス
interface TenpraRecipe
{
    // 天ぷらを得るメソッドの定義
    void getTenpra();
}

// 1つ目のアルゴリズム
class MakeTenpura implements TenpraRecipe
{
    public void getTenpra()
    {
        // 自分で天ぷらを揚げるアルゴリズムを実装する
        ・・・
    }
}

// 2つ目のアルゴリズム
class BuyTenpura implements TenpraRecipe
{
    public void getTenpra()
    {
        // 出来合いの天ぷらを使うアルゴリズムを実装する
        ・・・
    }
}

// 料理を作ってくれる店員
class Tenin
{
    // 現在のレシピを格納するフィールド
    TenpraRecipe currentReceipe;

    // レシピを変更するメソッド
    public void setReceipe(TenpraRecipe r)
    {
        this.currentReceipe = r;
    }

    // レシピの初期値を設定するコンストラクタ
    public Tenin(TenpraRecipe r)
    {
        this.currentReceipe = r;
    }

    // 料理を作るメソッド
    public void makeFood()
    {
        // 現在のレシピで天ぷらを得る
        currentReceipe. getTenpra();
        ・・・
    }
}

 クラスを使う人は、Tenin yazawa = new Tenin(new MakeTenpura()); のようにして店員のオブジェクトを生成します。このままyazawa.makeFood(); を呼び出せば、MakeTenpuraクラスのレシピで料理が作られます。yazawa.setReceipe(new BuyTenpura()); でレシピを変更してからyazawa.makeFood(); を呼び出せば、BuyTenpuraクラスのレシピで料理が作られます。

 「Strategyパターンって、ただ汎化してるだけじゃないの?」と思われるでしょう。でも、アルゴリズムを汎化するというアイディアが、なかなか奇抜なのです。ただし、評価は、コマンド(命令)を汎化するCommandパターンと同じく、星3つとさせていただきましょう。

 次回は、Template MethodパターンとVisitorパターンを紹介します。

矢沢久雄

グレープシティ株式会社(http://www.grapecity.com)アドバイザリースタッフ

表紙ページへ
■変更履歴
リスト1のサンプルソースで「class MakeTenpura」「class BuyTenpura」としていましたが、正しくはそれぞれ「class MakeTenpura implements TenpraRecipe」「class BuyTenpura implements TenpraRecipe」です。お詫びして訂正します。サンプルソースは修正済みです。[2013/05/15 18:05]