Pythonでデザインパターン実践:Command

環境

  • Windows 10
  • Python 3.10.1
  • VSCode

使用するソースコード

以下の公開リポジトリに置いています

GitHub - masayan1126/tao-py-py: pythonリポジトリ
pythonリポジトリ. Contribute to masayan1126/tao-py-py development by creating an account on GitHub.

Commandとは

  • commandとは何かの命令を意味します。このパターンでは、1つもしくは複数の命令を1つのオブジェクトで表現します
このパターンを適用すると、命令を追加が容易になり拡張性・再利用性が向上します

実装する内容

特定のフォルダを作成する処理をCommandとして実装する

クラス図

  • ICommand
    • 命令のインタフェース。Reciverのセッターとそれを実行するためのexecuteの実装を実装クラスに強制させる
  • MakeFolderCommand
    • ICommandを実装したクラス。具体的な命令をクラス名として定義する
  • IReceiver
    • Commandの処理対象となるオブジェクトのインタフェース
    • actionメソッドには実行したい処理を定義する
  • MakeFolderReciver
    • Receiverインタフェースを実装したクラス
    • actionメソッドには具体的な処理内容を定義する

Commandで定義されているインタフェースを呼出す起動者をInvokerとして構築することも可能ですが、今回はそこまでの構成にはしませんでした。
Invokerは一般的に、複数の「Command」をリストとして保持することにより、命令の履歴管理機能、UNDO機能等を実現することが可能です

実装

普通に書くとこのようになる

import os
os.makedirs("作成するフォルダのパス")
上記のようにわずか二行でフォルダが作成できるので、わざわざパターンを適用する必要はないのでは?と感じますが、ほかのパターンと同様、クラスが増えてきたりコード量が増えてくるとパターンに適用して実装するほうが保守性が向上します

まず、Commandクラスを抽象クラスで作成する

Commandクラスには、set_reciverとexecuteメソッドをかならず実装する必要があるように指定する

import abc
from shared.i_reciver import IReceiver

class ICommand:
    @abc.abstractmethod
    def __init__(self):
        pass

    @abc.abstractmethod
    def set_reciver(self, receiver: IReceiver):
        self.reciver = receiver

    @abc.abstractmethod
    def execute(self) -> None:
        pass

続いて、Commandインターフェースを実装する具象クラスを作成する

executeメソッド内でReciverクラスのactionを実行するように定義する

from shared.Domain.xfolder import XFolder
from shared.i_command import ICommand
from shared.i_reciver import IReceiver

class MakeFolderCommand(ICommand):
    def set_reciver(self, receiver: IReceiver):
        self.reciver = receiver

    def execute(self, xfolder: XFolder) -> None:
        self.reciver.action(xfolder)

次に、Receiverクラスを抽象クラスで作成する

import abc

class IReceiver:
    @abc.abstractmethod
    def action(self):
        pass

続いて、Reciverクラスを実装する具象クラス(MakeFolderReciver)を作成する

MakeFolderReciverのactionメソッドには、そのコマンドに実行させたい具体的な処理内容を定義する(今回の例では、osモジュールを使用してフォルダを作成する)
import os
from shared.Domain.xfolder import XFolder
from shared.i_command import ICommand

class MakeFolderReciver(ICommand):
    def __str__(self):
        pass

    def action(self, xfolder: XFolder) -> None:
        os.makedirs(xfolder.get_folder_path(), exist_ok=True)

なお、actionメソッドの引数として渡されるXFolderクラスは以下の通り

class XFolder:
    def __init__(self, base_path, folder_name):
        self.base_path = base_path
        self.folder_name = folder_name
       
    def get_base_path(self):
        return self.base_path

    def set_base_path(self, base_path):
        self.base_path = base_path
        return self

    def get_folder_name(self):
        return self.folder_name

    def set_folder_name(self, folder_name):
        self.folder_name = folder_name
        return self

    def get_folder_path(self):
        return self.base_path + self.folder_name

実際に呼び出してみる(※以下はテストコードです)

コマンド生成→レシーバーのセット→コマンドを実行の流れです

import os
import pytest
from packages.make_folder.Domain.make_folder_command import MakeFolderCommand
from packages.make_folder.Domain.make_folder_reciver import MakeFolderReciver

from shared.Domain.xfolder import XFolder
from shared.i_command import ICommand

@pytest.fixture
def setuped_command():
    command: ICommand = MakeFolderCommand()
    command.set_reciver(MakeFolderReciver())
    return command

def test_フォルダを作成できる(setuped_command: ICommand) -> None:
    basepath = ".\\tests\\"
    setuped_command.execute(XFolder(basepath, "hoge_dir"))
    assert os.path.exists(basepath + "hoge_dir")
pytestではfixtureを使用することにより、テスト前にテストで使用する値を事前に準備したり(setup)することが可能です

Python学習におすすめの書籍

独習Python/山田祥寛【3000円以上送料無料】
bookfan 1号店 楽天市場店
¥ 3,300(2022/02/05 17:20時点)
タイトルとURLをコピーしました