osiire’s blog

ふしぎなそふとやさん

GoF in OCaml for Advent Calendar 2012 #6

OCaml Advent Calendar用の記事、第六弾目です。構造に関するパターンの最後、Proxyパターンやります。

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

Proxyパターンは、インターフェイス経由で実体にアクセスすることで、間に一つ中間層を入れ込もうというパターンです。他のパターンでもそうでしたが、JavaでのインターフェイスOCamlではレコードにするとよいでしょう。

(* おまじない的補助関数. *)
let print s = Printf.printf "%s\n" s
let tee f x = f x; x
let (+>) f g = g f

(* Imageインターフェイスの代わり. 共通の操作を列挙する. *)
type image = {
    display : unit -> unit;
  }

module Image = struct
  type t = { fname : string; } (* 何か画像を表現するデータは省略.*)
  let loadImageFromDisk fname =
    (* なにか時間のかかるロード処理. *)
    print ("loading " ^ fname)

  let make fname =
    loadImageFromDisk fname;
    { fname }

  let display t =
    print ("displaying " ^ t.fname)

  (* 共通操作を抜き出す. *)
  let writer t = { display = fun () -> display t }

end

module ProxyImage = struct
  type t = {
      fname : string; (* Image.makeに渡す引数を取っておく. *)
      mutable cache : Image.t option; (* キャッシュ. *)
    }
  let make fname =
    { fname; cache = None }

  (* 共通操作を抜き出す. *)
  let writer t = {
    display = (fun () ->
      let image =
        match t.cache with
        | None ->
          (* キャッシュがなければ生成. *)
            Image.make t.fname
            +> tee (fun i -> t.cache <- Some i)
        | Some image ->
            image
      in
      Image.display image)
  }

end

let _ =
  let image1 = ProxyImage.writer (ProxyImage.make "HiRes_10MB_Photo1") in
  let image2 = ProxyImage.writer (ProxyImage.make "HiRes_10MB_Photo2") in
  let image3 = ProxyImage.writer (ProxyImage.make "HiRes_10MB_Photo3") in

  image1.display (); (* ここでロードされる. *)
  image2.display ();
  image2.display ()  (* ロード不要で表示.*)
  (* image3はロードされずに破棄される. *)

第一級のモジュールでやってもできますが、その場合には型tとdisplay関数を持つシグネチャーを定義する事になるでしょう。その型tがImage.tとProxyImage.tの二種類の型に対応する訳です。しかし、この型tには特に意味がない(他の型とユニファイする訳でもなく、戻り値の型になる訳でもない)ので、実際不要です。そこでdisplay関数だけのレコードで十分という判断になります。
レコードには型を隠す意味もあるんですね。
次回に続く!-> http://d.hatena.ne.jp/osiire/20121213