WEB系エンジニアの勉強日記

Baby steps to Giant strides!

Command pattern - Design Pattern in Ruby

「何かを実行するもの」をオブジェクトとして扱う方法。

これによる恩恵として、実行の記録の保管が容易になり、undo, redoなどの実装もしやすくなる。

よく使用されるケースとしては、GUIのパーツで、パーツのデザインや配置などの処理と、何かしらのアクションに応じた処理を切り離して管理することができる。

compositeパターンの考え方を用いてCompositeCommandを実装すると、複数のコマンドを単一のコマンドと同じように扱うことができる。

簡易実装

ボードにピースを配置するという簡単なプログラムで、ピースの追加、移動等の操作をCommandとして実装した。

class Board
  attr_accessor :pieces
  def initialize (size)
    @pieces = Array.new size
    @commands = []
    @command_index = 0
  end

  def execute_command(command)
    puts command.describe
    command.execute
    display_state
    @commands[@command_index] = command
    @command_index += 1
  end

  def undo
    puts 'Undo:'
    command = @commands[@command_index - 1]
    puts "\t" + command.describe
    command.unexecute
    display_state
    @command_index -= 1
  end

  def display_state
    state = '|'
    @pieces.each do |ele|
      if ele
        state << ele.name
      else
        state << '  '
      end
      state << '|'
    end
    puts state
  end
end

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

class CompositeCommand
  def initialize
    @commands = []
  end

  def add_command (command)
    @commands << command
  end

  def execute
    @commands.each {|c| c.execute}
  end

  def unexecute
    @commands.reverse.each {|c| c.unexecute}
  end

  def describe
    @commands.map{|c| c.describe}.join "\n"
  end
end

class AddPieceCommand
  def initialize (piece, index, board)
    @piece = piece
    @index = index
    @board = board
    @previousPiece
  end

  def execute
    @previousPiece = @board.pieces[@index]
    @board.pieces[@index] = @piece
  end

  def unexecute
    @board.pieces[@index] = @previousPiece
  end

  def describe
    "Add Piece: #{@piece.name} to #{@index}"
  end
end

board = Board.new 8

initial_command = CompositeCommand.new
initial_command.add_command AddPieceCommand.new Piece.new('A'), 2, board
initial_command.add_command AddPieceCommand.new Piece.new('B'), 4, board

board.execute_command initial_command
# Add Piece: A to 2
# Add Piece: B to 4
# |  |  |A|  |B|  |  |  |

board.undo
# Undo:
# Add Piece: A to 2
# Add Piece: B to 4
# |  |  |  |  |  |  |  |  |