Ceylonのインターフェースと抽象クラス
Ceylonにも抽象クラスとインターフェースがありますがJavaのにおけるそれとは若干異なります。今回は抽象クラスとインターフェースをテンプレートメソッドパターンを用いて紹介します。
よくある、String型のデータを受け取って、HTML、XML、コンソールいずれかに出力するサンプルを作成します。利用する側はWriterというインターフェースを利用し、インターフェースを実装したクラスでそれぞれの振舞いを定義(実装)します。
doc "Writer interface is based interface for all concrete Writer classes
which write the contents to the HTML or XML or ..."
shared interface Writer {
doc "Writes the contents passed as a parameter."
shared formal void write(String contents);
}
インターフェース定義は上記のように行います。sharedはいいとして、Javaとの違いはformalというアノテーションを付与する必要があります。メソッドの形式を宣言するアノテーションといった感じでしょうか。
次にこのインターフェースを実装した抽象クラスを定義します。
abstract class AbstractWriter() satisfies Writer {
shared default String title = "My document";
shared formal void writeHeader();
shared formal void writeFooter();
shared formal String decorateContents(String contents);
shared actual void write(String contents) {
writeHeader();
writeContents(decorateContents(contents));
writeFooter();
}
shared default void writeContents(String contents) {
print(contents);
}
}
Ceylonでインターフェースを実装するときはsatisfiesでインターフェースを定義します。複数のインターフェースを実装する場合は"&"で連結するようです。
先ほどインターフェースで宣言した、writeメソッドをここで実装しています。
このクラスの特徴は、writeHeader、writeFooter、decorateContentsという3つの抽象メソッドを定義しているということと、defaultアノテーションが付与されたtitleプロパティとwriteContentsメソッドを持っていることです。
このdefaultアノテーションを付与することにより、サブクラス側でプロパティ、メソッドのOverrideが可能となります。
Overrideと言いましたがCeylonでは、正確にはRefinementと呼びます。以降、Refinementといいます。
では、この抽象クラスを継承した3つのサブクラスを実装します。1つ目は内容をHTML形式で、2つ目はXMLで、3つ目はコンソール用にフォーマットを整形して出力するクラスを実装します。
class HtmlWriter() extends AbstractWriter() {
shared actual String title = "My html document";
shared actual void writeHeader() {
print("");
print("");
print("" + title + " ");
print("");
}
shared actual void writeContents(String contents) {
print("");
print(contents);
print("");
}
shared actual String decorateContents(String contents) {
value decorated = "[" + contents + "]";
return decorated;
}
shared actual void writeFooter() {
print("");
}
}
class XmlWriter() extends AbstractWriter() {shared actual String title = "My xml document";
shared actual void writeHeader() {
print(""); ");
print("" + title + " ");
}
shared actual void writeContents(String contents) {
print(""); ");
print(contents);
print("
}
shared actual String decorateContents(String contents) {
value decorated = "((( " + contents + " )))";
return decorated;
}
shared actual void writeFooter() {
print("
}
}
class ConsoleWriter() extends AbstractWriter() {
shared actual void writeHeader() {
print("@@@ " + title + " @@@");
}
shared actual String decorateContents(String contents) {
value decorated = "'" + contents + "'";
return decorated;
}
shared actual void writeFooter() {
print("@@@ end @@@");
}
}
HtmlWriterとXmlWriterは親クラスの抽象メソッドとdefault宣言を行っているtitleプロパティ、writeContents、いずれもサブクラス側でRefinementを行い、値と、振舞いを定義しています。
ConsoleWriterでは抽象メソッドのみRefinementを行っています。
抽象メソッド、default宣言のメソッド、プロパティをサブクラス側でRefinementする際は必ず、actualアノテーションを宣言する必要があります。つまり、親クラスでメソッド、プロパティ値の形式(formal)を定義し、サブクラス側で実体(actual)を定義するといった感じです。
CeylonでもJava同様に継承可能なクラスは1つのみです。
では、このクラスを実行するために以下のようなプログラムを用意します。
void main() {
Writer htmlWriter = HtmlWriter();
Writer xmlWriter = XmlWriter();
Writer consoleWriter = ConsoleWriter();
String originalContents = "Ceylon is a type safe language.";
print("===== output by the HTML =====");
htmlWriter.write(originalContents);
print("\n\n===== output by the XML =====");
xmlWriter.write(originalContents);
print("\n\n===== output to the console =====");
consoleWriter.write(originalContents);
}
利用する側(main)はWriterのインターフェースを利用して、その出力結果は実体(サブクラス)によって異なります。これはオブジェクト指向言語における、ポリモフィズムと呼ばれる概念です。
実行結果は以下のようになります。
===== output by the HTML =====
My html document
[Ceylon is a type safe language.]
===== output by the XML =====
My xml document
((( Ceylon is a type safe language. )))
===== output to the console =====
@@@ My document @@@
'Ceylon is a type safe language.'
@@@ end @@@
それぞれのオブジェクトにより実行結果のフォーマットがHTML、XML、コンソール用と分かれているのが見て取れます。
テンプレートメソッドパターンは、個人的にはオブジェクト指向言語でクラス、インターフェースの継承、ポリモフィズムといった概念を学ぶのに最適のパターンの1つだと思います。
あと、CeylonのインターフェースはMix-inをサポートしているようですが、現行のバージョンでは利用できないようです。次期リリースに期待と言ったところでしょうか。Mix-inは個人的にはかなりほしい機能の1つです。Rubyを使っていていいなと思う1つの理由がMix-inです。バイトコードを操作するライブラリを利用すればJavaでも一応実現できますが、はやり標準というのがいいです。
そういえば、以前、int a = 1;のような定義をしたらコンパイラに怒られたと紹介しましたが、言語仕様ではっきりとプリミティブ型は存在しないとありました。あとnullも存在しないようです。厳密にはNothingという型のようですが、これを説明するとUnionType等の話になってしまうのでとりあえずこの辺で...