🐡

Rubyで学ぶコマンドパターン (Command Pattern)

2025/01/18に公開

1. どんなもの?

コマンドパターンは、処理(操作)をオブジェクトとして抽象化し、リクエストをカプセル化するデザインパターンです。
これにより、操作の呼び出し元と実行元を分離し、操作を遅延実行したり、取り消し(Undo)を実現したりすることができます。

例えば、ユーザーインターフェースにおける「ボタン押下での処理」や、「キューイングされた操作の順次実行」などで活用されます。

2. 通常の実装方法と比べてどこがすごいの?

通常の方法

処理を直接クラス内に書くと、呼び出し元と実行元が密結合となり、柔軟性が失われます。

class Receiver
  def execute_action
    puts "Action executed"
  end
end

# 呼び出し元
receiver = Receiver.new
receiver.execute_action
  • 課題:
    • 呼び出し元が直接処理内容を知る必要があるため、結合度が高くなる。
    • 操作の履歴管理や取り消しが難しい。

コマンドパターンの利点

コマンドパターンを使うと、操作をオブジェクトとして分離できるため、柔軟性が向上します。

# コマンドの履歴を管理するクラス(操作の呼び出し元)
class CommandManager
  def initialize(receiver)
    @receiver = receiver
    @history = []
  end

  def execute_action
    @receiver.execute_action
    @history << :execute_action
  end

  def undo_last_action
    last_action = @history.pop
    if last_action == :execute_action
      puts "Undo action"
    end
  end
end

# 操作を実行するクラス(操作の実行元)
class Receiver
  def execute_action
    puts "Action executed"
  end
end

# 実行例
receiver = Receiver.new # 操作の実行元
manager = CommandManager.new(receiver) # 操作の呼び出し元

# コマンドの実行
manager.execute_action # => "Action executed"

# コマンドの取り消し
manager.undo_last_action # => "Undo action"
  • 利点:
    • 操作を独立したオブジェクトとして扱える。
    • 操作の履歴や取り消しを容易に実現可能。

3. 技術や手法の"キモ"はどこにある?

  1. 操作をオブジェクト化

    • 処理をオブジェクトとして抽象化することで、操作を一元管理できます。
  2. 柔軟な拡張性

    • 新しい操作を追加する際に、既存のコードに影響を与えることなく実現できます。
  3. 操作の履歴管理や取り消し

    • 実行済みのコマンドを記録しておくことで、履歴管理やUndo機能が実現可能です。

4. 実装例

例: Undo機能の実装

アプリで特定の操作を取り消す(Undo)機能を実装します。

class Command
  def execute
    raise NotImplementedError, "This method should be overridden in subclasses"
  end

  def undo
    raise NotImplementedError, "This method should be overridden in subclasses"
  end
end

class CreateUserCommand < Command
  def initialize(user_params)
    @user_params = user_params
    @user = nil
  end

  def execute
    @user = User.create(@user_params)
    puts "User created: #{@user.id}"
  end

  def undo
    @user.destroy if @user
    puts "User deleted: #{@user.id}"
  end
end

# Invoker(呼び出し元)
class Invoker
  def initialize
    @commands = []
  end

  def execute_command(command)
    @commands << command
    command.execute
  end

  def undo_last_command
    command = @commands.pop
    command.undo if command
  end
end

# 呼び出し元コード
invoker = Invoker.new
command = CreateUserCommand.new({ name: "Alice", email: "alice@example.com" })

invoker.execute_command(command) # => User created: 1
invoker.undo_last_command        # => User deleted: 1
  • 実装ポイント:
    • 操作をオブジェクトとして管理し、実行と取り消しを柔軟に実現。

5. 議論はあるか?

メリット

  • 操作を独立したオブジェクトとして扱えるため、再利用性や拡張性が向上。
  • 操作の履歴管理や取り消しが容易に実現可能。
  • 呼び出し元と実行元の依存を低減できる。

デメリット

  • コマンドクラスの数が増えすぎると管理が煩雑になる。
  • 操作が簡単な場合には、オーバーヘッドが大きくなる可能性がある。

議論

コマンドパターンは、柔軟性と拡張性を提供する強力なパターンですが、全ての操作に適用するとコードが冗長になるリスクがあります。適用範囲を慎重に見極めることが重要です。

6. まとめ

コマンドパターンは、操作をオブジェクト化することで、呼び出し元と実行元の分離を実現するデザインパターンです。

このパターンを活用することで、再利用性や拡張性を向上させることができますが、適用範囲を慎重に見極めることが重要です。

GitHubで編集を提案

Discussion