読者です 読者をやめる 読者になる 読者になる

osiire’s blog

ふしぎなそふとやさん

GoF in OCaml for Advent Calendar 2012 #8

OCaml Advent Calendar用の記事、第八弾目です。commandパターンいってみましょう。

http://en.wikipedia.org/wiki/Command_pattern

上記のリンクは英語版です。(日本語版にはサンプルコードが載ってなかったので。@mrgchrさん情報ありがとうございます!)

コマンドパターンの「コマンド」とは、何か実行したい処理とそのパラメーターをカプセル化したものです。そのオブジェクトのコレクションを扱う事で、undoを統一的に処理したり、優先順位を付けて処理したり、後で再生して利用したりする事ができるようになるらしいです。

とりあえず例として載っているコードをOCaml化してみましょう。

(* コマンドが持つべき関数. これのコレクションを扱う. *)
type command = {
  execute : unit -> unit;
}

module Switch = struct
  type t = command Queue.t (* OCamlの標準ライブラリにあるキューは破壊的. *)
  let make () = Queue.create ()
  let storeAndExecute t command =
     command.execute ();  (* コマンドを実行して、キューに入れておく. *)
     Queue.push command t
end

module Light = struct
  type t = unit (* 何か明かりを表現するものだけど、今回は省略 *)
  let make () = ()
  let turnOn t = Printf.printf "the light is on\n"
  let turnOff t = Printf.printf "the light is off\n"
end

module FlipUp = struct
  type t = Light.t
  let make t = t
  let execute t = Light.turnOn t
  let command t = { (* FlipUp.tからcommandレコードの値を作る. *)
    execute = fun () -> execute t
  }
end

module FlipDown = struct
  type t = Light.t
  let make t = t
  let execute t = Light.turnOff t
  let command t = {
    execute = fun () -> execute t
  }
end

let _ =
  let lamp = Light.make () in
  let switchUp = FlipUp.command (FlipUp.make lamp) in
  let switchDown = FlipDown.command (FlipDown.make lamp) in
  let switch = Switch.make () in
  match Sys.argv.(1) with
  | "ON" -> Switch.storeAndExecute switch switchUp
  | "OFF" -> Switch.storeAndExecute switch switchDown
  | _ -> ()

共通する部分をレコードとして切り出して使う構造としては、Proxyパターンと殆ど一緒です。
Switchモジュールが持つコマンド履歴が破壊的なところに注目してください。こういう状態を示すものはOCamlでも破壊的に扱う事がよくあります。もちろんStateモナドにしても構いませんが、プログラム全体がデータフローで完結するとき以外は使わないかもしれません。
次回に続く!