GoF in OCaml for Advent Calendar 2012 #3
三日坊主の三日目です。これで目標は果たせるので一安心です。
今日は構造パターンの一つDecoratorパターンやってみます。
http://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
Decoratorパターンは、特定の(Javaで言う)インターフェイスを満たすオブジェクトを受け取ってそれを拡張する事を目指したものです。インターフェイス経由で拡張するので色々なクラスのオブジェクトを拡張できます。
これをOCaml風に書き直すならば、次のようなコードになると思います。
(* Priceが満たすべき関数を定義 *) type price = { getValue : unit -> int; } (* 基礎的な価格付け *) let primePrice v = { getValue = fun () -> v; } (* 利益上乗せ価格 *) let wholesalePrice p advantage = { getValue = fun () -> p.getValue () + advantage; } (* 原価の二倍価格 *) let doublePrice p = { getValue = fun () -> (p.getValue ()) * 2 } (* 使い方 *) let _ = print_int ((wholesalePrice (doublePrice (wholesalePrice (doublePrice (primePrice 120)) 80)) 200).getValue ())
Priceインターフェイスがpriceレコード型に対応している点に注目してください。
特定の状態を内包するオブジェクト(インスタンス)を拡張するのがDecoratorパターンなので、OCamlでは状態を内包する高階関数を持つレコードに対応付けています。priceレコードをPriceモジュールにする事もできますが、OCamlでは第一級のモジュールを扱う文法はレコードを扱う文法より煩雑なので、型を持ち運ぶ必要がない限り使いません。
WholesalePriceモジュールを作るとしたら、次のような感じになります。
(* 利益上乗せ価格を扱うモジュール *) module WholesalePrice = struct type t = price * int (* インスタンスの生成 *) let make p advangate = p, advantage (* priceインターフェイスを持つインスタンスの生成 *) let makePrice p advantage = { getValue = fun () -> p.getValue () + advantage } end
price型の値を生成する関数を別途makePriceとして定義しています。モジュールには(通常は)状態を持たせないので、このような設計になります。
Javaでいうクラスとオブジェクト、OCamlのレコードとモジュールの使い方の違いが鮮明になって面白い例かも知しれません。
次回に続く! -> http://d.hatena.ne.jp/osiire/20121206