2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで構築するコマンドパターン:アクションの抽象化と履歴管理

Posted at

概要

**コマンドパターン(Command Pattern)**は、操作(アクション)をオブジェクトとして抽象化し、
実行、取り消し、再実行といった履歴管理や非同期処理、バッチ化などを容易にする設計パターンである。

本稿では、Pythonにおけるコマンドパターンの実装と応用に加えて、
Undo機能の構築やマクロ処理との連携など、実務に直結する設計戦略を紹介する。


1. なぜCommandが必要か?

❌ 呼び出し側が操作の中身を知っている

editor.save()
editor.undo()
editor.print_document()

→ 呼び出し側が editor の振る舞いをすべて知っており、疎結合性が崩壊


✅ 操作そのものを「コマンド」として抽象化

command = SaveCommand(editor)
invoker.execute(command)

→ 実行対象(editor)や操作の詳細から 呼び出し元を切り離せる


2. 基本構造

✅ コマンドインタフェース

class Command:
    def execute(self): ...
    def undo(self): ...

✅ 具体的なコマンド

class AddTextCommand(Command):
    def __init__(self, editor, text):
        self.editor = editor
        self.text = text

    def execute(self):
        self.editor.add(self.text)

    def undo(self):
        self.editor.remove_last()

✅ 呼び出し元(Invoker)

class CommandInvoker:
    def __init__(self):
        self.history = []

    def execute(self, command):
        command.execute()
        self.history.append(command)

    def undo(self):
        if self.history:
            self.history.pop().undo()

3. 使用例:簡易エディタ

class TextEditor:
    def __init__(self):
        self.content = []

    def add(self, text):
        self.content.append(text)

    def remove_last(self):
        if self.content:
            self.content.pop()

    def show(self):
        print("".join(self.content))
editor = TextEditor()
invoker = CommandInvoker()

invoker.execute(AddTextCommand(editor, "Hello "))
invoker.execute(AddTextCommand(editor, "World!"))
editor.show()  # Hello World!

invoker.undo()
editor.show()  # Hello 

4. 応用パターン:バッチ化・マクロ処理

class MacroCommand(Command):
    def __init__(self):
        self.commands = []

    def add(self, command):
        self.commands.append(command)

    def execute(self):
        for c in self.commands:
            c.execute()

    def undo(self):
        for c in reversed(self.commands):
            c.undo()

→ 一連の操作を 一つのコマンドとして扱う
→ マクロ録音・再生機能に応用可能


5. 非同期コマンドの設計(asyncio対応)

class AsyncCommand:
    async def execute(self): ...
    async def undo(self): ...

→ タスクキュー、ジョブ実行制御、スケジューラとの親和性が高い


6. 実務ユースケース

  • テキストエディタのUndo/Redo機構
  • GUIアプリでのボタン動作の動的バインディング
  • バッチ実行処理のキューイングとリトライ
  • ゲーム開発におけるユーザー操作の記録・再生

7. よくある誤用と対策

❌ コマンドが実行対象の内部を操作しすぎる

→ ✅ コマンドは 1つの動作単位に徹するべき。副作用や状態変更の責務を分離


❌ Undo実装を省略して履歴が中途半端に

→ ✅ undo() を最初から設計に組み込み、再現可能性・整合性を担保


❌ コマンド生成の責務が呼び出し元に集中

→ ✅ CommandFactory や DI で生成を抽象化


結語

コマンドパターンは、アクションをオブジェクト化することで、
操作の追跡・記録・巻き戻し・再実行を可能にする構造
である。

  • 単一責任で定義されたコマンドにより、操作の可搬性と再利用性が高まる
  • 呼び出し元との疎結合を維持しながら、振る舞いを動的に切り替える設計が可能
  • Undo機能、マクロ処理、非同期ジョブといった高度なアプリケーション要件に自然に適応

Pythonicとは、“操作を動作としてではなくデータとして設計する”ことであり、
コマンドパターンはその構造的思考を最も象徴的に表す手段である。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?