はじめに
Commandパターンを試す記事です。 以前から存在は知っていたのですが、実際に試したことはなかったので、実際に試してみました。
サンプルプロジェクト github.com
そもそもCommandパターンとは
処理の呼び出しと実装側を分けることを目的としたデザインパターンで、処理で呼び出される命令をオブジェクト単位で分けて実装するとのこと。オブジェクト単位で管理することで、UndoやRedoが実現しやすくなるみたいです。
登場する役割としては以下のものがあります
名前 | 役割 |
---|---|
Command | 命令(オブジェクト単位) |
Receiver | 依頼を受け取り、実行される。処理対象 |
Invoker | 命令を実行する |
Client | 命令呼び出しを依頼 |
流れとしては、ClientがCommandをNew()して、CommandをInvokerに渡し、Invokerの実行メソッドを呼ぶことでInvokerにCommandを依頼します。Invokerでは、渡されたCommandオブジェクトをスタックにあげて管理します。その後に、Commandの実行関数を呼び、Receiverに定義されたメソッドを呼ばれることでClientがしたい事を実現する と云った感じです。
上記で色々言いましたが、Commandパターンを実装する方法は様々なようで、Wikipediaでは
Commandが内包する処理の記述は開発者に一任されている。全ての処理をCommand内に記述するパターン[4]、あるいはcommandインスタンス生成時に実行対?象(Receiver)を受け取りcommand.Execute()時にreceiver.action()コールのみをおこなう、すなわち実行をReceiverに委譲してCommandは繋ぎに徹するパターン もある[5]。尚 Receiver は Commandパターンの要件ではない。
Command パターン - Wikipedia とあるように、色々あります。次で説明するサンプルは、Receiverを委譲してCommandはつなぎに徹するパターンで制作しました。
実際に試してみる
今回は、公式が出しているサンプルを基に以下の物を作りました。移動とRedoができるサンプルです。
ICommand
ここでCommandの命名メソッドを定義しています。
public interface ICommand { void Execute(); void Undo(); }
IReceiver
public interface IReceiver { void Move(Vector3 pos); }
Client
ここでボタンとLeftMove(),RightMove(),Redo()を結び付けて、右・左どちらの方向に移動したいのかを指示できるようにしています。
public class Client : MonoBehaviour { [SerializeField] private Button _rightMoveButton; [SerializeField] private Button _leftMoveButton; [SerializeField] private Button _undoButton; [SerializeField] private PlayerReceiver _receiver; private void Start() { _rightMoveButton.onClick.AddListener(LeftMove); _leftMoveButton.onClick.AddListener(RightMove); _undoButton.onClick.AddListener(Undo); } private void LeftMove() { ICommand command = new MoveCommand(_receiver,Vector3.left); Invoker.Execute(command); } private void RightMove() { ICommand command = new MoveCommand(_receiver,Vector3.right); Invoker.Execute(command); } private void Undo() { Invoker.Undo(); } }
Invoker
ここで、命令を実行しています。Stackを使ってCommandをまとめて管理しており、このStackを使ってRedoの実装を可能にしています。
public class Invoker { private static Stack<ICommand> _commands = new Stack<ICommand>(); public static void Execute(ICommand command) { command.Execute(); _commands.Push(command); } public static void Undo() { if (_commands.Count > 0) { ICommand command = _commands.Pop(); command.Undo(); } } }
PlayerReceiver
ここでIReceiverを実装しています。Clientから受けた指示を基に、命令を実行します。
public class PlayerReceiver : MonoBehaviour,IReceiver { public void Move(Vector3 pos) { transform.position += pos; } }
MoveCommand
ここでICommandを実装していています。あくまでReceiverを繋ぐことに徹しています。
public class MoveCommand : ICommand { private PlayerReceiver _receiver; private Vector3 _pos; public MoveCommand(PlayerReceiver receiver,Vector3 pos) { _receiver = receiver; _pos = pos; } public void Execute() { _receiver.Move(_pos); } public void Undo() { _receiver.Move(-_pos); } }
おわりに
Commandパターンは、パズルゲームやリバーシで使われているそうで、今回は実装しませんでしたが、Undo機能も追加出来たりと色々できます。
是非、読者登録をしていただくと助かります!