Processingで学ぶ 実践的プログラミング専門課程

第25回 デザインパターン(3) Strategy

この記事を読むのに必要な時間:およそ 4 分

導入

同じことをするにもいろんな方法があるものです。トンカツの添え物キャベツは千切りがよい人,角切りがよい人,手でちぎったのがよい人……。結局口に入って,消化され栄養を吸収された後は,繊維質が残って排出されます。どの方法でも結果は同じです。⁠トンカツとともにキャベツを食べる」という目的のための「キャベツの切断方法」⁠それは好みと少々の意味合いによって,人それぞれに選択をします。

さて,今回は目的に応じたアルゴリズムの選択・切り替えを,柔軟に行えるコードを書くためのデザインパターンであるStrategyパターンを紹介します。GoF本(P.vii)でも初学者向けの題材であると勧められています。

展開

Strategy(ストラテジ)

プログラムは何らかの目的があって作られます。目的を果たすためにアルゴリズムをコードに落とします。そのアルゴリズムは1つとは限りません。アルゴリズムによって処理速度の違いや効率の善し悪しがあります。あるデータに対して高速だったアルゴリズムが,別のデータに対して低速であることは珍しくありません。ですから,データに応じてアルゴリズムを切り替えれば,実行速度の低下を防ぐことができるでしょう。

Strategyパターンの目的

Strategyという単語は,アルゴリズム(algorithm)という単語よりも広いニュアンスを持ちます。ここではアルゴリズムという単語の意味を「コンピュータに仕事を行わせる手順」とします。Strategyはコンピュータに限らず問題解決のための「戦略,戦術,方策」という意味があります。少々軍事的なニュアンスがありますが,アルゴリズムと改まって表現する程でもない手順や約束事も,広くとらえればStrategyです。

目的のために使用できるアルゴリズムが複数あるとします。場合に応じてアルゴリズムを切り替えて使用できれば便利です。アルゴリズムを部品化して,柔軟に切り替えて使用できる仕組みをまとめたのがStrategyパターンです。

また,アルゴリズムを表現するコードAを,それ利用する側のコードBから分離した場合,BからAのコードの細部は見える必要がない場合がほとんどです。必要なのは,Aが目的の出力をしてくれることです。AとBが一体となり,コードの細部があらわになっていると,コードが読みづらくなります。BからAのフィールドに対して不用意な操作を受けるかもしれません。これらを回避するためにも,Strategyパターンを適用してアルゴリズムをカプセル化する利点があります。

[Strategyパターン]
主となるコードからアルゴリズムを表現するコードを切り離し,部品化する。コードの実行時にアルゴリズムの切り替えを可能にする。

Strategyパターンのクラス図

画像

上図のStrategyパターンでは,Clientが計算結果を必要とし,Contextクラスに対して使用するアルゴリズム(ConcreteStrategy)を指定します。そして,ClientクラスはContextクラスのcontextMethodを呼んで必要な結果を得ます。

アルゴリズムを実装したConcreteStrategyクラスは複数あります。ConcreteStrategyクラスはStrategyインタフェイスを実装しています。

ContextクラスはStrategyインタフェイスを通してConcreteStrategyクラスのメソッドstrategyMethodを使用します。Contextクラスのコードでは,ConcreteStrategyクラスの実装がどのようなものかを知る必要がありません。Strategyインタフェイスを通じてメソッドstrategyMethodを呼べれば良いのです。

今後,異なるアルゴリズムを実装した新しいConcreteStrategyクラスを使い始めたならば,Clientが新しいクラスを使用することをContextクラスに対して指定するだけで,その他のコードについて変更が必要ない仕組みになっています。大変柔軟でシンプルな構造です。

Strategyパターンを使わないと

同じ目的のために作られた,異なるアルゴリズムを切り替えて使う場合,Strategyパターンを使わなければ,次のコードのように条件判断文で使用するメソッドを切り替えることになるでしょう。これは1からnまでの数の合計を取るコードです。異なるアルゴリズムを実装した2つのメソッドを交互に切り替えています。

同じ目的の異なるアルゴリズムを実装した複数のメソッドを,条件判断文で切り替えて使うMultiAlgorithm.pde

void setup(){
  int n = 10;
  println("Sum of 1 to " + n + " is ..."); 
  println("Switch algorithm.");
  for ( int i = 0; i < n; i++ ){
    int result = (i%2 == 0) ? sumSlow(n)
                            : sumFast(n);
    println(i + " : sum(" + n + ") = " +  result);
  }
}

int sumSlow(int n){
  println("<sumSlow>");
  int val = 0;
  for(int i=0; i <= n; i++){
    val = val + i;
  }
  return val;
}

int sumFast(int n){
  println("<sumFast>");
  int val = n * (n+1) / 2;
  return val;
}
アルゴリズムをクラスでカプセル化する

アルゴリズムを実装したメソッドを,クラスでカプセル化すると次のように書けます。このように書くメリットは,アルゴリズムをカプセル化したクラスを,部品として再利用できることです。

アルゴリズムをカプセル化したSlowSumFastSumクラスのコードは紙面の都合上省略しますので,リンク先のファイルで確認してください。

条件判断文で使用するクラスを切り替える部分のコード(コード全体はNoStrategyPattern.pdeを参照)

void setup(){
  int n = 10;
  println("Sum of 1 to " + n + " is ..."); 
  println("Switch algorithm.");
  SlowSum ss = new SlowSum();
  FastSum fs = new FastSum();
  for ( int i = 0; i < n; i++ ){
    int result = (i%2 == 0) ? ss.sum(n)
                            : fs.sum(n);
    println(i + " : sum(" + n + ") = " +  result);
  }
}

著者プロフィール

平田敦(ひらたあつし)

地方都市の公立工業高等学校教諭。趣味はプログラミングと日本の端っこ踏破旅行。やがては結城浩氏のような仕事をしたいと妄想している。

Twitter : @hirata_atsushi

コメント

コメントの記入