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側にも反映したければクラス自体を差し替えることになり、それは色々まずかった記憶が。
そのあたりも確認する必要がありますねぇ・・・ということで、今回はここまでです。