Command
Rubyによるデザインパターン生活。継続中。
本日はCommandパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
CommandパターンのCommand(コマンド)とは命令の意味です。
この本には、
コマンドはある特定の何かをするための命令です。
と定義されています。
あ、今回からクラス図をアップすることにしました。
それではCommandパターンのクラス図です。
Commandパターンはとてもシンプルな構成になっています。
上記のクラス図では、抽象的なCommandクラス(インタフェース)を実装する、
ConcreteCommand1、ConcreteCommand2クラスを示しています。
Commandインタフェースにはexecuteメソッドが定義されています。
このexecuteメソッドが「ある特定の何かをするための命令」になります。
今回のサンプルに登場するクラスです。
- Command
- Commandクラス。名前そのままでCommandパターンのCommandを担当します。
- CreateFile
- Commandのサブクラス。ファイルを作成するクラス。
- DeleteFile
- Commandのサブクラス。ファイルを削除するクラス。
- CopyFile
- Commandのサブクラス。ファイルをコピーするクラス。
※CreateFile、DeleteFile、CopyFileクラスは、ConcreteCommand1(2)クラスのように振る舞います。
- CompositCommand
- 複数のコマンドを集約するクラス。自身もCommandのサブクラスであり、保持する複数のコマンドをまとめて実行します。
つづいて、ソースコードです。
require "fileutils" class Command attr_reader :description def initialize(description) @description = description end def execute end end class CreateFile < Command def initialize(path, contents) super("Create file : #{path}") @path = path @contents = contents end def execute f = File.open(@path, "w") f.write(@contents) f.close end end class DeleteFile < Command def initialize(path) super("Delete file : #{path}") @path = path end def execute File.delete(@path) end end class CopyFile < Command def initialize(source, target) super("Copy file : #{source} to #{target}") @source = source @target = target end def execute FileUtils.copy(@source, @target) end end class CompositCommand < Command def initialize @commands = [] end def add_command(cmd) @commands << cmd end def execute @commands.each { |cmd| cmd.execute } end def description description = "" @commands.each { |cmd| description += cmd.description + "\n"} description end end cmds = CompositCommand.new cmds.add_command(CreateFile.new("file1.txt", "hello world\n")) cmds.add_command(CopyFile.new("file1.txt", "file2.txt")) cmds.add_command(DeleteFile.new("file1.txt")) cmds.execute
Commandクラスとそのサブクラスを定義したあと、CompositCommandオブジェクトを生成し、
CreateFile、CopyFile、DeleteFileオブジェクトを追加しています。
その後、追加したCommandオブジェクトを追加した順にまとめて実行しています。
実行結果は、カレントディレクトリに作成したfile1.txtがコピーされてfile2.txtとなります。
また、コピー元となったfile1.txtは削除されます。
結果としてfile2.txtのみが残ります。
また、さきほどのプログラムの最後で、
puts cmds.description
と実行すると、画面に集約したコマンドの一覧を出力します。
Create file : file1.txt Copy file : file1.txt to file2.txt Delete file : file1.txt
ここまでの流れをみると、
一つひとつのCommandの処理はexecuteメソッドを実装するだけで単純なものです。
「executeメソッドを実装した」から「Commandパターンだ」と思っていてはいけません。
本に載っている大事な部分を引用します。
Commandパターンのポイントは、何を行うかの決定と、それの実行を分離することです。
このパターンを使う場合、「これを行え」と命令する代わりに、「これを行う方法を記録しろ」と命令し、その後「記録したことを行え」と命令します。
Commandパターンは命令と実行の分離と考えると良いと思います。
また、Commandパターンの応用編として、
Undo(やりなおし)の実装も紹介されています。
さきほどのプログラムの各Commandクラスにunexecuteメソッドを追加してみます。
require "fileutils" class Command attr_reader :description def initialize(description) @description = description end def execute end end class CreateFile < Command def initialize(path, contents) super("Create file : #{path}") @path = path @contents = contents end def execute f = File.open(@path, "w") f.write(@contents) f.close end def unexecute File.delete(@path) end end class DeleteFile < Command def initialize(path) super("Delete file : #{path}") @path = path end def execute if(File.exists?(@path)) @contents = File.read(@path) end File.delete(@path) end def unexecute f = File.open(@path, "w") f.write(@contents) f.close end end class CopyFile < Command def initialize(source, target) super("Copy file : #{source} to #{target}") @source = source @target = target end def execute if(File.exists?(@target)) @contents = File.read(@target) end FileUtils.copy(@source, @target) end def unexecute File.delete(@target) if(@contents) f = File.open(@target, "w") f.write(@contents) f.close end end end class CompositCommand < Command def initialize @commands = [] end def add_command(cmd) @commands << cmd end def execute @commands.each { |cmd| cmd.execute } end def unexecute @commands.reverse.each { |cmd| cmd.unexecute } end def description description = "" @commands.each { |cmd| description += cmd.description + "\n"} description end end cmds = CompositCommand.new cmds.add_command(CreateFile.new("file1.txt", "hello world10\n")) cmds.add_command(CopyFile.new("file1.txt", "file2.txt")) cmds.add_command(DeleteFile.new("file1.txt")) cmds.execute cmds.unexecute
最後にcmds.unexecuteメソッドを呼び出すことで、
コピー処理の一連の流れを取り消すことができます。