Tbpgr Blog

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

マルチスレッドデザインパターン | Balking 〜勇者はメタルスライムとだけ戦います

概要

Balking

詳細

Balking=立ち止まる、躊躇する、引き返すの位。野球のボークもこの単語。
実行されると困る条件の場合、実行せず処理の手前で実行を中止する。
実行可能な条件の際に実行するGuarded Suspentionパターンと反対の発想。
Guarded Suspention同様ガード条件を利用して処理を実装する。

このパターンが有用なケースは以下
・特定の条件で処理が不要になるようなケースにムダな処理を省く

要件によりbalkに対する処理を変える
・処理が不要なら何もしない
・balkの発生を知らせる必要がある場合
・何かしら戻り値で知らせる
・例外で知らせる

サンプル仕様

あるRPGの世界を想定。
・敵は1回の戦闘で必ず5体のグループで現れます
・魔王が0.05秒に1回、エンカウントするモンスターを創り出します
・勇者は0.1秒に1回、戦闘を行います。ただしメタルスライムを含まない敵グループとは戦いません
※メタルスラムを含まない=Balkingのガード条件にあたる

UML

魔王シーケンス

勇者シーケンス

クラス構成

Monster : モンスターを表すデータクラス
GameSystem : ゲームシステム。モンスターの生成とメタルスライム限定戦闘コマンドを管理します。
GuardedObjectにあたります。
Devil : 魔王。0.05秒に1回モンスターを創りだす流れ作業者
Hero : 勇者。0.1秒に1回魔王が生成したモンスターにメタルスライムが含まれるかチェックし、
含まれている時だけ戦闘する鬼畜作業者。

サンプルソース

# encoding: utf-8
require "pp"
require "thread"

class Monster
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end

def get_random_1_to_100
  rand(100) + 1
end

class GameSystem
  attr_accessor :monsters
  MONSTER_MASTER = [
                    Monster.new("スライム"), 
                    Monster.new("スライムベス") , 
                    Monster.new("バブルスライム"),
                    Monster.new("キングスライム"),
                    Monster.new("メタルスライム")
                  ]

  def initialize
    @m = Mutex.new
    @monsters = []
    create_monster
  end

  def create_monster
    @m.synchronize {
      @monsters = []
      5.times {
        rand = get_random_1_to_100
        case rand
        when 1..40
          @monsters << MONSTER_MASTER[0]
        when 41..70
          @monsters << MONSTER_MASTER[1]
        when 71..89
          @monsters << MONSTER_MASTER[2]
        when 90..99
          @monsters << MONSTER_MASTER[3]
        else
          @monsters << MONSTER_MASTER[4]
        end
      }
      puts "create! @monsters"
      @monsters
    }
  end

  def fight_only_metal_slime
    @m.synchronize {
      puts "call fight"
      return unless has_metal_slime?
      msg = []
      @monsters.each do |monster|
        msg << "#{monster.name}を倒した"
      end
      puts msg.join("\n")
    }
  end

  def has_metal_slime?
    @monsters.include?(MONSTER_MASTER[4])
  end
end

# 魔王
class Devil < Thread
  def initialize(game_system)
    block = lambda {
      20.times {
        game_system.create_monster
        sleep 0.05
      }
    }
    super(&block)
  end
end

# 勇者
class Hero < Thread
  def initialize(game_system)
    block = lambda {
      10.times {
        game_system.fight_only_metal_slime
        sleep 0.1
      }
    }
    super(&block)
  end
end

game_system = GameSystem.new
threads = []
threads << Devil.new(game_system)
threads << Hero.new(game_system)
threads.each {|t|t.join}

結果

ruby balking.rb
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
$ruby balking.rb
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
create! @monsters
create! @monsters
call fight
スライムを倒した
スライムを倒した
スライムベスを倒した
メタルスライムを倒した
スライムベスを倒した
create! @monsters