Strategy Pattern - Design Pattern in Ruby Chapter 5

www.amazon.co.jp

まとめ

Templateパターンと同じ問題を解決するが、compositionとdelegateの形式で行う分、疎結合で柔軟な設計になる。


似たようなことを行うオブジェクト(family objects)をまとめ同じインターフェースを定義することで、使う側のオブジェクト(context object)から、簡単に入れ替えができるようになる。


rubyの場合、シンプルなStrategy patternはProcオブジェクトベースで実現できる。


Strategyパターンを用いるときに注意することは、contextとstrategyのインターフェース。


1つ目のstrategyオブジェクトを複雑にタイトに連携したインターフェースにしてしまうと、それ以降のstrategyオブジェクトをうまく扱えなくなってしまう。

ProcベースのStrategy Patternの実施

Array#bubble_sort!のオーダー順をProcベースのStrategy Patternで実装してみた。

class Array

  def bubble_sort! (&ordering)

    (self.size - 1).times do     
      for i in 0...(self.size - 1)
        if ordering.call(self[i], self[i + 1]) > 0
          tmp = self[i + 1]
          self[i + 1] = self[i]
          self[i] = tmp
        end
      end
    end
  end
end

array = [1, 3, 10, 9]
array.bubble_sort! {|a, b| a <=> b}
# [1, 3, 9, 10]

array.bubble_sort! {|a, b| b <=> a}
# [10, 9, 3, 1]

extends core Date - Rails code reading

activesupport/lib/active_support/core_ext/date/conversions.rb

to_sで指定できるフォーマットを拡張している。

一番上に対応するフォーマットが定義されている。

class Date
  DATE_FORMATS = {
    :short        => '%d %b',
    :long         => '%B %d, %Y',
    :db           => '%Y-%m-%d',
    :number       => '%Y%m%d',
    :long_ordinal => lambda { |date|
      day_format = ActiveSupport::Inflector.ordinalize(date.day)
      date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
    },
    :rfc822       => '%d %b %Y',
    :iso8601      => lambda { |date| date.iso8601 }
  }

formatが文字列かlamdaかをrespond_to?(:call) で判別している。

def to_formatted_s(format = :default)
    if formatter = DATE_FORMATS[format]
      if formatter.respond_to?(:call)
        formatter.call(self).to_s
      else
        strftime(formatter)
      end
    else
      to_default_s
    end
  end
  alias_method :to_default_s, :to_s
  alias_method :to_s, :to_formatted_s

activesupport/lib/active_support/core_ext/date/calculations.rb

Date#+が日の加算処理、Date#>>が月の加算処理

# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
  # any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
  def advance(options)
    options = options.dup
    d = self
    d = d >> options.delete(:years) * 12 if options[:years]
    d = d >> options.delete(:months)     if options[:months]
    d = d +  options.delete(:weeks) * 7  if options[:weeks]
    d = d +  options.delete(:days)       if options[:days]
    d
  end

React.jsで将棋盤を作ってみた

React,Reduxのキャッチアップ - エンジニアの勉強日記

上記記事で基本をキャッチアップしたので、もう少し実践的なプログラムを作ろうということで、将棋盤を作ってみた。

GitHub - kitabatake/react-shogi

構成

  • webpackでes6, jsxをトランスパイル

  • viewコンポーネントにReactを使用

  • reduxは使わず

はじめはreduxも使おうとしたけど、reducer, action周りをまだ上手い具合に実装できなさそうだったのと、 Practical Object Oriented Design in Ruby (以降POOD) を読んだばかりで、その辺りの知見を実践してみたいということで、Reactのみの薄い構成で作ってみた。

reduxはHPの実践サンプルをいくつか見てみた後に実装してみたい。

実装の流れ

まずViewComponentの実装は固めやすそうということで、ViewComponentと表示するのに必要な情報を定義。

この辺りreduxの影響をだいぶ受けていて、stateを持つstoreを作成して、変更を検知してレンダリングする構成で実装。

UIイベントもクリックされたセルのx, yをfireするだけというように、なるべく責任範囲を小さくするように意識した。


POODでいうドメインオブジェクトとして、盤、駒、駒台の他に、

  • selectKoma
  • candidatesMovablePositions(駒)
  • move(駒, x, y)
    • movable?(駒, x, y)

この辺りのメッセージに応えるクラスとして進行役的な役割が必要かなと考えた。

あとViewComponentからのイベントを進行役に伝搬するイベント処理の役割が必要かなと考えた。

この段階で全体的な設計を一旦決定。


いろいろな条件での駒の移動範囲の取得や、成るかどうかの判断、駒を取る処理など、進行役の責任範囲が大きくなってきてしまい、ごちゃごちゃしてきてしまったので、

駒の移動範囲の判定などの別のオブジェクトに移して、処理を委譲する形に修正。

その後は割とすんなりと実装できた。

振り返り

将棋は駒の種類が多いのと、細かいルールが結構あって処理が複雑になりがちなので、OODのトレーニングとしては結構良さそう。

Reactに関しては、最初にReactComponentを作成して、stateをsubscribeさせた後はほぼいじらなかったので、View周りほぼノンストレスで開発できた。

facilitator周りが煩雑になってしまった部分

進行役という役割を見つけたのは良かったけど、実装する前に進行役に送られるメッセージをリストアップしてみることで、責任の肥大化はある程度予測できて、あらかじめ構成要素を探すことができたかも?

ただ実装していく中で、必要なメッセージがどんどん解ってくるのは間違いないので、前もって予測するのは簡単ではなさそう。

何か責任が大きくなってきているかなと感じた時に、しっかりと問題と向き合うことが大事?

設計を見つめ直さずに「とりあえず動かす」というのを優先しがち。

POODで書いてあったように、開発を進めていく中でプログラムに関する知識が増えていき、明確になっていくので、最初に決めた設計でちょっと実装しづらいなと思った時は、すぐに一旦冷静に設計を見つめ直す意識が大事。

設計を壊してもいいという前提で、各オブジェクトを疎結合にして適切なテストを書いておけば、設計が変わっても部品レベルでは再開発しなくて済むようにできる。

Chapter9 - Practical Object Oriented in Ruby

Designing Cost Effective Tests

まとめ

効果的なテストを書くことの目的は、これまでやってきた設計に関する考えと同じで、コストを減らすこと。


テストはあるオブジェクトに対して、どういった文脈で、どのように扱えばいいかを最小限の形で記述するので、オブジェクトに対してのいいドキュメントとなる。


デザインの決定を遅らせるという文脈において、現状あまり良くないコードだけど修正するにはもう少し待っておいた方がいいという場合等に、インターフェースだけはしっかり定義して、そのインターフェースに対するテストをしっかり書いておくことで、いざ修正しようとした時にスムーズに行うことができる。


When the design is bad, testing is hard.

あるテストを書くことのセットアップ等が複雑で困難な場合、アプリケーション内でそのオブジェクトを使うことも難しい。


スタブやモックを使ったテストやprivate method, duck type, 継承周りのテストのやり方や注意点など結構ボミューミーに説明されている。

この辺りは何が書かれているかを把握しておいて、後で必要になった時にしっかりと読んでみようと思う。

Chapter 9 - Practical Object Oriented Design in Ruby

Combining Object with Composition

まとめ

1つのオブジェクトを複数のオブジェクトで構成するという話。

自転車オブジェクトがPartsオブジェクト持つという例で、sparesのようなPartsオブジェクトが返答すべきメッセージは内部でPartsオブジェクトへ処理を委譲する。

外から見たら同じだけど、内部を複数のオブジェクトに分割、管理することで、SRPに基づいた設計ができ、再利用性や拡張に対して柔軟な設計となりやすくなる。

複数のオブジェクトから構成されるオブジェクトは、初期化処理が複雑になりがち。

そういった場合はFactoryパターンを活用すると、オブジェクト生成に関する知識を1つの場所に収めることができる。

is-a, has-a, behaves-like-a のどの関係性がふさわしいか、それぞれの実装のinheritance, composition, duck types(module) が持つ長所、短所を理解して判断する必要がある。

これらの判断は、情報が少ない開発初期の段階では間違いがちなので、それを受け入れて正しく修正して心構えが大事。 初期の実装にひきづられずに、情報が増えてきた際に正しく判断し、リファクタリングをしていく。

Chapter 7 - Practical Object Oriented in Ruby

Sharing Role Behavior with Modules

まとめ

dock typeの章で説明したような共通の振る舞いを発見した際に、それをモジュール化することで、コードを1つの箇所に置くことができる。

シーケンス図の各ノードに、具体的なクラス名を書くのではなく、求められていること、実際にやっていることを記述することで、モジュール化する役割の名前を見つ出すことができることがある。


ある1つのことに関して実現させようとするとき、それ以外のことを知っていないと実現できようなケースは余計な依存性があると考えることができる。

例えば、文字列に関して、空がどうかの判定にStringUtils.empty?(str)とする必要がある場合、単純にstring.empty?とできるケースと比較すると、StringUtils#empty?の存在を知っていないといけないという依存性ができる。

stringのようなコアのオブジェクトに関しては、頻繁に変更が起きることもないし、基本的な使い方はすぐに覚えることができてるのでそれほど大きな問題にはならないと思うけど、アプリケーションに定義したオブジェクトでこういった余計な依存性が増えていくと、やりたいことに対して、実現させるコストが増えそうだし、変更のコストも結構変わってきそう。


抽象的な役割を見つけた時に、対処しなければいけない問題として、どうやってその役割を実現させるかと、どこにそのコードを置くかという2つが発生する。

こういったケースでは問題を分けて対処するのがわかりやすくて、まず1つのクラスに対して、役割を実現できるようなコードを実装した後に、抽象化させる部分をモジュールに移して動作させ、その後に他の同じ役割のクラスでも動作させるようにするといい。


同じ役割を持つクラスは、その役割を果たす工程において、クライアントからは全て同じインターフェースに対応する必要がある。(同じメソッド名、引数、戻り値)

同じ役割を持っているのに、クライアントで、どのクラスかどうかで処理を分けるのはなるべく避けるべき。


継承、モジュールを使うと、構成をどんどん複雑化させることができるので注意して扱う必要がある。

これらの技術はデザイン設計の技術よりコーディング作業に密接に連携しているので、うまくできるどうかで、アプリケーションに対する影響は大きい。

Chapter 6 - Practical Object Oriented Design in Ruby

Acquiring Behavior Through Inheritance

まとめ

継承とはメッセージを自動的に転送すること。


継承を用いると、クラスの構造が複雑になるので、必要な情報が揃った時に行うのがいい。

具体的には、2つの似たような種類のクラスがあって、1つの親クラスと、2つのサブクラスに分けられそうな場合は、いくつかコードが重複してしまっていても、そのままにしておいた方がいい。(もちろんケースバイケース)

似たような属性を持つクラスが3つになった時には、親子構造を作成するための情報をかなり持つことができるので、適切な設計を作りやすくなる。(必要な情報が入るまで、現状のままにしておくことは、コストマネージメントを考える上で大事)


いざ親クラスを作ろうとなった時は、まず空の親クラスを作成し、継承関係を設定した後に、サブクラスから親クラスへ振る舞いをボトムアップさせていくやり方がいい。

この方法の場合は、サブクラスに抽象化すべきところが残ってしまう可能性がある。

ただ、抽象化しないとサブクラス間でコードが重複してしまう→親クラスにまとめるというのは自然に行われていくので、それほど大きな問題にはなりにくい。

逆の場合、先に親クラスに元のクラスをコピーし、サブクラス固有の振る舞いをサブクラスに移行するやり方の場合は、親クラスに本来サブクラスであるべき具体的な振る舞いが残ってしまう可能性がある。

このケースを修復するのは前者より難解になりやすく、親クラスに手をつけずにコードを拡張していくと、純粋な継承関係が崩壊してしまい、システム全体へと影響が広がってしまう可能性がある。


サブクラスからsuperを使って明示的に親クラスのメソッドを呼び出すのは、1つの依存性と言える。 依存性があるということは、変更時のコストに影響がある。

hookやtemplate patternなどを使うことで、依存性を避けるように、サブクラスのスペシャリティーを簡潔に表現できる設計方法を探すといい。