osiire’s blog

ふしぎなそふとやさん

GoF in OCaml for Advent Calendar 2012 #4

OCaml Advent Calendar用の記事、第四弾目です。今日はFacadeパターンいってみましょう。

http://ja.wikipedia.org/wiki/Facade_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

Facadeパターンは、関連するクラス群を使用するための手続きを、窓口となる一つのクラスに集約するらしいです。

  • 関連するクラス群を外部から隠蔽するためにパッケージスコープでアクセス制限する。
  • Facadeクラスに記述された処理が、コードの重複を防いだり処理の見通しを良くしてくれる。

OCamlにはパッケージ名前空間はないので(2012年冬現在)、外部から隠蔽したい関連モジュールはFacadeモジュールに含めればいいでしょう。

module DrivingSimulator : sig
  (* 外部にはsimulate関数しか見せない. *)
  val simulate : unit -> unit
end = struct
  external (|>) : 'a -> ('a -> 'b) -> 'b = "%revapply"
  (* 4.00.0未満は次のコードを使いましょう. *)
  (* let (|>) f g = g f  *)

  (* モジュール内モジュール. 
   * DrivingSimulatorのシグネチャーに記述されていないので、外部からは見えない.
   *)
  module Car = struct
      type t = { speed : int; distance : int }
      let make speed distance = { speed; distance; }
      (* 破壊的代入はしない。tが引数の最後にある事に注目 *)
      let run minutes t = { t with distance = t.distance + minutes * t.speed }
      let setSpeed speed t = { t with speed = speed }
      let distance t = t.distance
  end

  module Driver = struct
    type t = Car.t
    let ride car = car
    let pushPedal speed t = Car.setSpeed speed t
    let drive minutes t = Car.run minutes t
  end

  let simulate () =
    (* パイプライン演算子で次々と処理を繋げる。
     * CarやDriverに破壊的代入をせずに書くときはこういう書き方をする.
     *)
    Driver.ride (Car.make 0 0)
    |> Driver.pushPedal 700
    |> Driver.drive 30
    |> Driver.pushPedal 750
    |> Driver.drive 20
    |> (fun car ->
        Printf.printf "The travel distance is %d m." (Car.distance car))
end

CarやDriverに破壊的代入が使われていない点に注目してください。破壊的代入の代わりに、setSpeedやpushPedalは新しいCar.tやDriver.tを返しています。こういう新しい値を返す関数の場合、元の値を引数の最後に持ってきます。そうすると、simulate関数にあるようなパイプ演算子で繋げる関数プログラミングっぽいスタイルで記述できるようになります。

これパターンって呼んでいいのかな・・・。まぁいいか。
次回に続く! -> http://d.hatena.ne.jp/osiire/20121210