Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

RubyでMediatorパターン

概要

GoFデザインパターンのMediatorパターンについて。
多数のメンバー(同僚、Colleague)を利用する処理を調停者(Mediator)を通して行うパターン。
個別のメンバーにばらばらに重複するような処理を持たせずに一か所で管理することで
保守性を向上させる。また、メンバーの追加も容易になる。

登場人物

Mediator = 調停者
ConcreteMediator = 具象調停者
Colleague = 同僚
ConcreteColleague1= 具象同僚1
ConcreteColleague2= 具象同僚2

UML


実装サンプル

サンプル概要

Todo管理を行うHTML生成ツールを想定します。
通常のTodo文字列を表すTodoTextは文字列と実行済みフラグを保持します。
重要なTodo文字列を表すImportantTodoTextは太字で表示され、文字列と実行済みフラグを保持します。
実行済みの場合は取り消し線付きでテキストを表示します。

通常Todo文字列もしくは重要Todo文字列タスクの実行有無が変更された場合
・進捗率を表す数字の表示を更新
・進捗率を表すバーの表示を更新
を行います。

登場人物

Mediator = TodoManager:TODO管理の調停者。
ConcreteMediator = HtmlTodoManager:HTMLによるTODO管理の調停者。
Colleague = TodoParts:Todoの構成要素。同僚役。
ConcreteColleague = TodoText:タスク文字列。
ConcreteColleague1= NormalTodoText:通常タスク文字列。具象同僚1役。
ConcreteColleague2= ImportantTodoText:重要タスク文字列。具象同僚2役。
ConcreteColleague3= TodoProgressNumber:進捗を表す数字(now/maxで表示)具象同僚3役。
ConcreteColleague4= TodoProgressBar:進捗を表すバー。完了部は緑。未完了部は赤で全体の比率を表す。具象同僚4役。

UML


サンプルコード
出力結果

todo_manager

# encoding: Shift_JIS

=begin rdoc
= TodoManagerクラス
=end
class TodoManager
  NOT_OVERRRIDE = 'not override error'

  def add_todo_parts(todo_parts)
    raise NOT_OVERRRIDE
  end
  
  def change_complete(todo_parts)
    raise NOT_OVERRRIDE
  end
end

HtmlTodoManager

# encoding: Shift_JIS
require_relative './todo_manager'
require_relative './normal_todo_text'
require_relative './important_todo_text'

=begin rdoc
= HtmlTodoManagerクラス
=end
class HtmlTodoManager < TodoManager
  attr_accessor :todo_parts_map,:todo_text_list
  TODO_LIST="todo_list"
  PROGRESS_NUMBER="progress_number"
  PROGRESS_BAR="progress_bar"
  
  def initialize()
    @todo_parts_map = Hash.new
    @todo_text_list = Array.new
    @todo_parts_map[TODO_LIST] = @todo_text_list
  end
  
  def add_todo_parts(key,todo_parts)
    if (todo_parts.class == NormalTodoText) || (todo_parts.class == ImportantTodoText)
      @todo_parts_map[key].push(todo_parts)
    else
      @todo_parts_map[key] = todo_parts
    end
  end
  
  def change_complete()
    html = ""
    todo_parts_map[HtmlTodoManager::PROGRESS_NUMBER].update todo_text_list
    html << todo_parts_map[HtmlTodoManager::PROGRESS_NUMBER].html
    todo_parts_map[HtmlTodoManager::PROGRESS_BAR].update todo_text_list
    html << todo_parts_map[HtmlTodoManager::PROGRESS_BAR].html
    @todo_text_list.each{|todo_parts|
      if todo_parts.class == NormalTodoText
        if todo_parts.is_complete
          todo_parts.html="#{NormalTodoText::COMPLETE_START}#{todo_parts.text}#{NormalTodoText::DIV_END}\n"
        else
          todo_parts.html="#{NormalTodoText::NOT_COMPLETE_START}#{todo_parts.text}#{NormalTodoText::DIV_END}\n"
        end
      elsif todo_parts.class == ImportantTodoText
        if todo_parts.is_complete
          todo_parts.html="#{ImportantTodoText::IMP_COMPLETE_START}#{todo_parts.text}#{ImportantTodoText::IMP_DIV_END}\n"
        else
          todo_parts.html="#{ImportantTodoText::IMP_NOT_COMPLETE_START}#{todo_parts.text}#{ImportantTodoText::IMP_DIV_END}\n"
        end
      end
      html << todo_parts.html
    }
    puts html
  end
end

TodoParts

# encoding: Shift_JIS

=begin rdoc
= TodoPartsクラス
=end
class TodoParts
  attr_accessor :todo_manager,:is_complete,:html
  NOT_OVERRRIDE = 'not override error'

  def initialize(todo_manager)
    @todo_manager = todo_manager
  end

  def update()
    raise NOT_OVERRRIDE
  end
  
  def set_complete(is_complete)
    raise NOT_OVERRRIDE
  end
end

TodoText

# encoding: Shift_JIS
require_relative './todo_parts'

=begin rdoc
= TodoTextクラス
=end
class TodoText < TodoParts
  attr_accessor :text
  NOT_OVERRRIDE = 'not override error'

  def initialize(todo_manager,text)
    super(todo_manager)
    @text=text
  end

  def set_complete(is_complete)
    @is_complete=is_complete
    @todo_manager.change_complete
  end
end

NormalTodoText

# encoding: Shift_JIS
require_relative './todo_text'

=begin rdoc
= NormalTodoTextクラス
=end
class NormalTodoText < TodoText
  NOT_COMPLETE_START="<div style='text-decoration:none;'>"
  COMPLETE_START="<div style='text-decoration:line-through;'>"
  DIV_END="</div>"
end

ImportantTodoText

# encoding: Shift_JIS
require_relative './todo_text'

=begin rdoc
= ImporantTodoTextクラス
=end
class ImportantTodoText < TodoText
  IMP_NOT_COMPLETE_START="<strong><div style='text-decoration:none;'>"
  IMP_COMPLETE_START="<strong><div style='text-decoration:line-through;'>"
  IMP_DIV_END="</div></strong>"
end

TodoProgressNumber

# encoding: Shift_JIS
require_relative './todo_parts'

=begin rdoc
= ProgressNumberクラス
=end
class ProgressNumber < TodoParts
  def update(todo_text_list)
    complete_count=0
    uncomplete_count=0
    
    todo_text_list.each{|todo|
      if todo.is_complete
        complete_count+=1
      else
        uncomplete_count+=1
      end
    }
    @html="<div>進捗状況:#{complete_count}/#{complete_count+uncomplete_count}</div>\n"
  end
end

TodoProgressBar

# encoding: Shift_JIS
require_relative './todo_parts'

=begin rdoc
= ProgressBarクラス
=end
class ProgressBar < TodoParts
  def update(todo_text_list)
    complete_count=0
    uncomplete_count=0
    
    todo_text_list.each{|todo|
      if todo.is_complete
        complete_count+=1
      else
        uncomplete_count+=1
      end
    }
    complete_percent = complete_count.to_f/(complete_count+uncomplete_count).to_f
    complete_percent = complete_percent*100
    uncomplete_percent = 100-complete_percent
    @html="<meter min='0' value='#{complete_percent}' max='100'>#{complete_percent}%</meter>"
  end
end

main

# encoding: Shift_JIS
require_relative './html_todo_manager'
require_relative './todo_parts'
require_relative './normal_todo_text'
require_relative './important_todo_text'
require_relative './progress_number'
require_relative './progress_bar'

manager = HtmlTodoManager.new
progress_number = ProgressNumber.new(manager)
manager.add_todo_parts(HtmlTodoManager::PROGRESS_NUMBER,progress_number)
progress_bar = ProgressBar.new(manager)
manager.add_todo_parts(HtmlTodoManager::PROGRESS_BAR,progress_bar)

todo1 = NormalTodoText.new(manager,"normal_todo1")
manager.add_todo_parts(HtmlTodoManager::TODO_LIST,todo1)
todo2 = NormalTodoText.new(manager,"normal_todo2")
manager.add_todo_parts(HtmlTodoManager::TODO_LIST,todo2)
important_todo1 = ImportantTodoText.new(manager,"important_todo1")
manager.add_todo_parts(HtmlTodoManager::TODO_LIST,important_todo1)
important_todo2 = ImportantTodoText.new(manager,"important_todo2")
manager.add_todo_parts(HtmlTodoManager::TODO_LIST,important_todo2)

todo1.set_complete(false)
puts "<hr />"
todo2.set_complete(true)
puts "<hr />"
important_todo1.set_complete(true)
puts "<hr />"
important_todo2.set_complete(true)
puts "<hr />"
todo1.set_complete(true)
puts "<hr />"

出力

<div>進捗状況:0/4</div>
<meter min='0' value='0.0' max='100'>0.0%</meter><div style='text-decoration:none;'>normal_todo1</div>
<div style='text-decoration:none;'>normal_todo2</div>
<strong><div style='text-decoration:none;'>important_todo1</div></strong>
<strong><div style='text-decoration:none;'>important_todo2</div></strong>
<hr />
<div>進捗状況:1/4</div>
<meter min='0' value='25.0' max='100'>25.0%</meter><div style='text-decoration:none;'>normal_todo1</div>
<div style='text-decoration:line-through;'>normal_todo2</div>
<strong><div style='text-decoration:none;'>important_todo1</div></strong>
<strong><div style='text-decoration:none;'>important_todo2</div></strong>
<hr />
<div>進捗状況:2/4</div>
<meter min='0' value='50.0' max='100'>50.0%</meter><div style='text-decoration:none;'>normal_todo1</div>
<div style='text-decoration:line-through;'>normal_todo2</div>
<strong><div style='text-decoration:line-through;'>important_todo1</div></strong>
<strong><div style='text-decoration:none;'>important_todo2</div></strong>
<hr />
<div>進捗状況:3/4</div>
<meter min='0' value='75.0' max='100'>75.0%</meter><div style='text-decoration:none;'>normal_todo1</div>
<div style='text-decoration:line-through;'>normal_todo2</div>
<strong><div style='text-decoration:line-through;'>important_todo1</div></strong>
<strong><div style='text-decoration:line-through;'>important_todo2</div></strong>
<hr />
<div>進捗状況:4/4</div>
<meter min='0' value='100.0' max='100'>100.0%</meter><div style='text-decoration:line-through;'>normal_todo1</div>
<div style='text-decoration:line-through;'>normal_todo2</div>
<strong><div style='text-decoration:line-through;'>important_todo1</div></strong>
<strong><div style='text-decoration:line-through;'>important_todo2</div></strong>
<hr />

出力内容をHTMLとして保存した画面