Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Ruby | Delivering Output | Yield Status Object

概要

Yield Status Object

前提

Confident Rubyではメソッド内の処理を次のように分類しています。
・Collecting Inputs(引数チェック、変換など)
・Performing Work(主処理)
・Delivering Outpu(戻り値に関わる処理)
・Handling Failure(例外処理)

当記事は上記のうち、Delivering Outputに関する話です。

詳細

状況

コマンドメソッドが成功・失敗以上の成果物を欲しがるかもしれない。
また、その値を戻したくない。

概要

コールバック形式のStatusオブジェクトを利用したメソッドの成果物を表す。

理由

このアプローチは、成果物を与えるために何をすべきか綺麗に分かれる。
また、Return Status Objectと異なりCommand/Query Separation principleにも反していない。

サンプルコード仕様

信号を表すクラスSignalを作成します。
Signal#signal_statusでランダムに信号の状態を返却します。

サンプル1
SignalStatusクラスを作成します。信号の状態を保持するとともに、
・信号の状態確認用メソッド(SignalStatus#red?, SignalStatus#green?, SignalStatus#vanished?)
・信号の状態別用インスタンス取得メソッド(SignalStatus.red, SignalStatus.green, SignalStatus.vanished)
を持ちます。

Signal#signal_statusの戻り値をSignalStatusクラスの各状態別インスタンスで返却します。
この場合、利用側はSignalStatusが外部公開している振る舞いを元に条件を記述することができます。

サンプル2
・SignalStatusクラスにイベント処理用のメソッドon_red,on_green,on_vanishedを追加します。
・Signal#signal_statusの戻り値の部分をyieldに変更します。

サンプルコードその1

require 'pp'

module Traffic
  class SignalStatus
    RED = ""
    GREEN = ""
    VANISHED = "消灯"

    attr_reader :color

    def self.green
      new(GREEN)
    end

    def self.red
      new(RED)
    end

    def self.vanished
      new(VANISHED)
    end

    def initialize(color)
      @color = color
    end

    def green?
      @color == GREEN
    end

    def red?
      @color == RED
    end

    def vanished?
      @color == VANISHED
    end
  end

  class Signal
    def self.signal_status
      begin
        case signal_random
        when 1
          SignalStatus.red
        when 2
          SignalStatus.green
        end
      rescue => e
        return SignalStatus.vanished
      end
    end

    def self.signal_random
      ret = rand(3).to_i + 1
      raise 'invalid' if ret == 3
      ret
    end
  end
end

10.times do
  signal_status = Traffic::Signal.signal_status
  if signal_status.green?
    puts "信号が青なので渡ります"
  elsif signal_status.red?
    puts "信号が赤なので待ちます"
  elsif signal_status.vanished?
    puts "信号がついていないが渡ります"
  end
end

出力(ランダム)

信号がついていないが渡ります
信号が青なので渡ります
信号がついていないが渡ります
信号が青なので渡ります
信号が青なので渡ります
信号が青なので渡ります
信号がついていないが渡ります
信号が青なので渡ります
信号がついていないが渡ります
信号が赤なので待ちます

サンプルコードその2

require 'pp'

module Traffic
  class SignalStatus
    RED = ""
    GREEN = ""
    VANISHED = "消灯"

    attr_reader :color

    def self.green
      new(GREEN)
    end

    def self.red
      new(RED)
    end

    def self.vanished
      new(VANISHED)
    end

    def initialize(color)
      @color = color
    end

    def on_green
      yield if @color == GREEN
    end

    def on_red
      yield if @color == RED
    end

    def on_vanished
      yield if @color == VANISHED
    end
  end

  class Signal
    def self.signal_status
      begin
        case signal_random
        when 1
          yield SignalStatus.red
        when 2
          yield SignalStatus.green
        end
      rescue => e
        yield SignalStatus.vanished
      end
    end

    def self.signal_random
      ret = rand(3).to_i + 1
      raise 'invalid' if ret == 3
      ret
    end
  end
end

10.times do
  Traffic::Signal.signal_status do |result|
    result.on_green { puts "信号が青なので渡ります" }
    result.on_red { puts "信号が赤なので待ちます" }
    result.on_vanished { puts "信号がついていないが渡ります" }
  end
end

出力(ランダム)

信号が青なので渡ります
信号が赤なので待ちます
信号がついていないが渡ります
信号が青なので渡ります
信号がついていないが渡ります
信号が赤なので待ちます
信号がついていないが渡ります
信号がついていないが渡ります
信号が青なので渡ります
信号がついていないが渡ります