Iterator pattern - Design Pattern in Ruby

複数の要素を持つオブジェクトが、それぞれの要素に順繰りにアクセスする方法を提供するパターン。

  • External iterator

    has_next, next_item のような順繰りに必要なメソッドを提供することで、外部からの要素へのアクセスを可能にする。

    loop処理などを自分で書く必要があるが、その分柔軟な記述が可能

  • Internal iterator

    要素を引数にもったコードブロックを渡して、内部で各要素付きで実行してもらう。

    簡潔に処理したいコードを書くことができる。

実装

eachを実装して、Enumerableをincludeするミニマムのコードを書いてみた。


class Player
  attr_reader :time
  def initialize (name, time)
    @name = name
    @time = time
  end

  def <=> (other)
    @time <=> other.time
  end
end

class PlayerGroup
  include Enumerable
  def initialize
    @players = []
  end

  def add_player (player)
    @players << player
  end

  def each (&block)
    @players.each {|p| yield p}
  end
end

pg = PlayerGroup.new
pg.add_player Player.new 'kiryuu', 10.0
pg.add_player Player.new 'bolt', 9.58
pg.add_player Player.new 'gatorin', 9.81

p pg.sort

Composite pattern - Design Pattern in Ruby Chapter 6

共通のオペレーションを行う要素と、その要素の集合体を同じように扱うことで、複雑な構造をシンプルに扱う。

用語

  • Component 共通のインターフェース

  • Leaf ミニマムの要素

  • Composite 要素の集合体

注意点

compositeとleaf,異なる属性のものを全く同じように扱うのは無理。

目的に沿って、うまく設計する必要がある。

実践

ViewCompnentをサイズを共通オペレーションとして持つような想定で実装.

class ViewComponent
  attr_accessor :name, :position
  def initialize(name, position)
    @name = name
    @position = position
  end
  def get_size
    {
      width: 0,
      height: 0
    }
  end
end

class Element < ViewComponent
  def get_size
    {
      width: 50,
      height: 100
    }
  end
end

class ViewContainer < ViewComponent
  def initialize(name, position)
    super(name, position)
    @children = []
  end

  def add_child(child)
    @children << child
  end

  def get_size
    width = 0
    height = 0
    @children.each do |child|
      w = child.position[:x] + child.get_size[:width]
      h = child.position[:y] + child.get_size[:height]
      width = w if w > width
      height = h if h > height
    end
    {
      width: width,
      height: height
    }
  end
end

box = ViewContainer.new 'Box', {x: 10, y: 10}
box.add_child Element.new 'ele1', {x:20, y: 20}
p box.get_size
# {:width=>70, :height=>120}

box.add_child Element.new 'ele2', {x:100, y: 200}
p box.get_size
#{:width=>150, :height=>300}

Observer Pattern - Design Pattern i Ruby Chapter 5

注意点

  • 更新頻度を考える。 考えなしにすべての変更時にobserverオブジェクトに通知すると、かなりの数の冗長な通知が飛び交ってしまう可能性がある。 どういったタミングで通知を行うかの戦略は考えておくべき。

  • 変更の一貫性 上記の高新頻度にも似ているが、細かい情報の変更を全て通知していると、整合性が取れなくなるケースが考えられる。 どういった変更のまとまりで通知するかを考えておく必要がある。

バリエーション

ruby標準のobserver

module Observable (Ruby 1.8.7)

updateメソッドを実装したobserverオブジェクトを定義することで簡単に実装できる。


コードベースのobserver

observerオブジェクトにupdateメソッドを実装したクラスのオブジェクトではなく、Procオブジェクトを使うことで、より簡単にObserverオブジェクトを登録できる。


updateメソッドの引数

一番簡単な実装はシンプルにselfを渡す実装だけど、この場合はobserverがobservableオブジェクトの内容から変更箇所を特定するような処理が必要になったりする。

こういったことが問題になるようなケースでは、変更箇所ごとに細かいupdate_***系のメソッドを用意するといい。

ただこの場合だと標準のobserverモジュールが使えないため、自分でmoduleを定義する必要がある。

実践

NewsFeedの通知を受け取るような想定で実装

require 'observer'

class NewsFeed
  include Observable
  def initialize
    @news = []
    @next_news_id = 1
  end

  def add_news(title)
    @news << {
      id: @next_news_id,
      title: title
    }
    @next_news_id += 1
    changed
  end

  def publish
    notify_observers self
  end

  def getNewsAfter(id)
    @news.select {|news| news[:id] > id}
  end
end

class NewsListener
  def initialize
    @last_received_news_id = 0
  end

  def update(news_feed)
    unread = news_feed.getNewsAfter @last_received_news_id
    @last_received_news_id = unread.last[:id]
    p unread
  end
end

news_feed = NewsFeed.new
news_feed.add_observer NewsListener.new

news_feed.add_news 'news1'
news_feed.publish

news_feed.add_news 'news2'
news_feed.add_news 'news3'
news_feed.publish

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, 継承周りのテストのやり方や注意点など結構ボミューミーに説明されている。

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