夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Clojure勉強日記(その26 インタフェース/プロトコル

1.インタフェース

Javaの場合前回の投稿のようなケースをどうするかというとインタフェースを切って、
実装クラスを追加することで拡張する方式を取ります。

インタフェースは以下のような利点があります。

  • 実体クラスは複数のインタフェースを実装することができる
  • インタフェースは使用のみを指定し、実装を提供しない。そのため、多重継承の問題が発生しない。

ただ、インタフェースにも欠点があり、既存の実体クラスに新たなインタフェースを追加する場合、
元の実体ソースを書き変える必要があることです。
・・・Java屋だと「そんなの当然じゃん。」と思ってしまうのですが、とりあえず進めます。

尚、ClojureでもJavaのインタフェースはdefinterfaceマクロで作成することが可能です。

definline clojure.core
(macro)

Argument Lists:
[name & decl]

Documentation:
Experimental - like defmacro, except defines a named function whose
  body is the expansion, calls to which may be expanded inline as if
  it were a macro. Cannot be used with variadic (&) args.
(definterface IOFactory
  (^java.io.BufferReader makeReader [this])
  (^java.io.BufferedWriter makeWriter [this]))

上記のコードはIOFactoryというインタフェースを定義し、
BufferReaderを返すmakeReader/BufferedWriterを返すmakeWriterのメソッドを保持します。

後はこれを用いてJavaなりClojureで継承すればいいという形になります。

ただ、先ほども書いていますが、あるクラスが実装するインタフェースはクラスの作者がクラスを実装した時点で確定します。
そのため、Javaのクラスは一度定義するとソースを修正しない限りインタフェースは追加できません。
結果、File/Socketといったクラスに上記のインタフェースをサポートしてもらうことはできません。

これをExpression Problemというそうですが・・・
ともあれ、これがClojureでどう解決されるか見てみましょう。

2.プロトコル

Expression Problemへの解の一つとしてClojureには「プロトコル」があります。
インタフェースと同じように実装抜きで使用だけを指定し、データ型が複数プロトコルを実装できるというのは共通です。
その上で、「既にある型に対して元のコードをいじることなく新たなプロトコルを追加できる」というのがインタフェースに対する優位点となります。

defprotocol clojure.core
(macro)

Argument Lists:
[name & opts+sigs]

実際のIOFactoryをプロトコルで定義すると以下のようになります。

(defprotocol IOFactory
"汎用の読込み/書込みを定義したプロトコル"
(makeReader  [this] "Creates a BufferedReader.")
(makeWriter  [this] "Creates a BufferedWriter."))

上記のIOFactoryを用いて実際に既存クラスを拡張した例が以下になります。

(extend-type File
     IOFactory
     (makeReader [src] (-> src FileReader.  BufferedReader.))
     (makeWriter [dst] (-> dst FileWriter. BufferedWriter.)))

・・・という形で拡張できるようです。
ただ、これもClojure内で限定的に使用できる・・・のだとは思いますが。
Java側にも反映したければクラス自体を差し替えることになり、それは色々まずかった記憶が。

そのあたりも確認する必要がありますねぇ・・・ということで、今回はここまでです。