Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

ラムダ式でStrategyパターンで実装されたコードをシンプルにする ~ そこから見えてくるストラテジオブジェクトの本質

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング 第2回

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2015/01/20 14:00

 Java SE 8で追加された構文要素「ラムダ式」を使うと、デザインパターンで解決しようとしていた問題を、ずっと素直なコードで実装できます。本連載では、デザインパターンを使って書かれたコードを、ラムダ式を使ってシンプルなコードに書き換えながら、ラムダ式の使いどころ・使い方を学んでいきます。今回行うのは、Strategyパターンの書き換えです。

目次

Strategyパターンのおさらい

 Strategyパターンは、ひとまとまりの計算をストラテジオブジェクトとして表すパターンです。Strategyパターンを使うことで、必要に応じて計算法を切り替えることが容易になります。典型的なStrategyパターンのプログラムには、図1に示すようなクラスおよびインタフェースが登場します。

図1:Strategyパターンのクラス図
図1:Strategyパターンのクラス図

 

 それぞれのクラス、インタフェースの役割は次のとおりです。

  • Strategy:計算を表すストラテジオブジェクトのインタフェース
  • ConcreteStrategy:具体的な計算を表すストラテジオブジェクトのクラス
  • Context:ストラテジによる計算を用いた処理を行うクラス
  • クライアント:Contextにストラテジを登録するクラス[1]

[1] クライアントの役割は、GoF[1994](書籍『オブジェクト指向における再利用のためのデザインパターン』)には列挙されていませんが、本稿では説明を簡潔にするためにこれを採用します。

 典型的なStrategyパターンのプログラムは、図2に示すようなシーケンスで動作します。

図2:Strategyパターンのシーケンス図
図2:Strategyパターンのシーケンス図

 

 すなわち、Strategyパターンのプログラムはおおまかに次の2ステップで動作します。

  1. ストラテジオブジェクトを Context に登録する
  2. Context がストラテジオブジェクトによる計算を用いて処理を行う

 Strategyパターンを用いている例には、Javaコレクションフレームワークの TreeSet クラスがあります[2]TreeSet クラスは順序付き集合を表すコレクションクラスで、NavigableSet インタフェースを実装しています。要素の格納順を指定したいときには、TreeSet クラスのコンストラクタに Comparator インタフェースの引数を与えます。TreeSet クラスは、Comparator#compare メソッドを呼び出して要素間の大小関係を調べ、その結果に従って要素を格納します。

 たとえば、従業員を表す Employee クラスのインスタンスを、TreeSet に名前順で格納するプログラムは、リスト1のように書けます。

リスト1:EmployeeStorer.java
import java.util.*;

/** 従業員. */
class Employee {
    private final String name;
    Employee(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
}

/** 従業員を名前で比較するストラテジ. */
class NameComparator implements Comparator<Employee> {
    @Override
    public int compare(Employee x, Employee y) {
        return x.getName().compareTo(y.getName());
    }
}

public class EmployeeStorer {
    public static void main(String[] args) {
	// 従業員を名前順で格納する集合
        NavigableSet<Employee> employees = new TreeSet<>(new NameComparator());
        employees.add(new Employee("Stephen"));
        employees.add(new Employee("Aggi"));
        employees.add(new Employee("Katrina"));

        // 従業員を名前順で出力する
        employees.forEach(e -> System.out.println(e.getName()));
        // 出力: Aggi <改行> Katrina <改行> Stephen
    }
}

 TreeSet に関して、Strategyパターンに登場する役割は、それぞれ次のようなクラス・インタフェースによって担われています。

  • Strategy:Comparator インタフェース。
  • ConcreteStrategy:Comparator インタフェースを実装するクラス。【例】リスト1の NameComparator クラス。
  • Context:TreeSet クラス。
  • クライアント:TreeSet クラスのインスタンスを作るクラス。【例】リスト1の EmployeeStorer クラス。

 TreeSet クラスなどは、Strategyパターンを用いることで、次のような設計意図を実現しているものと考えられます。

  • フレームワークが、ストラテジオブジェクトによって表される計算方法を知っている必要がない
  • フレームワークを用いるプログラムが、新たな計算方法を定義できる

Template Methodパターンでも実現できる

 実は、このような設計意図は、図3のようにクラスの継承を使ったTemplate Methodパターンでも実現できます。

図3:Template Methodパターンのクラス図
図3:Template Methodパターンのクラス図

 

 Template Methodパターンにおいて、AbstractClass は、計算を行うメソッドを抽象メソッドとして提供し、その計算を用いる処理のメソッドを実装します。ConcreteClassAbstractClass を継承し、具体的な計算を行うメソッドを実装します。

 Template Methodパターンには、登場するインタフェース・クラスが少なくて済むという長所があります。一方でStrategyパターンには、計算を記述する ConcreteStrategy クラスを、計算を用いた処理を記述する Context クラスと継承関係にする必要がなく、完全に独立させられるという長所があります。このようにそれぞれに長所があるため、どちらを採用するべきかは場合によります。

 TreeSet クラスと Comparator インタフェースの場合には、ConcreteStrategyContext から独立していることの長所が活かせるため、Strategyパターンを採用していることが正解だといえるでしょう。

 たとえば、リスト1の NameComparator クラスは、TreeSet クラスと継承関係になく、完全に独立しています。このため NameComparator クラスは、順序付きマップを表す TreeMap クラスや、リストをソートする Collections#sort メソッドなど、Comparator インタフェースのインスタンスをストラテジオブジェクトとして用いる他のクラスやメソッドと一緒に用いることができます。

 Template Methodパターンについては、本連載で回を改めて取り上げます。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 宮川 拓(ミヤカワ タク)

    日本Javaユーザーグループ幹事。東京のシステムインテグレータに勤務。Java VM上で動作する言語である「Kink」を開発中。相撲とアメリカ文学とスコティッシュポップを愛する。 ・ブログ: http://d.hatena.ne.jp/miyakawa_taku/ ・Twitter: @...

バックナンバー

連載:デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング
All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5