今年は先のエントリはおしまいかと思っていたら、コメントを頂いてしまったので、またもや「はてな」からのコピーです。
「直接インターフェイスをnewする」ネーミングが決められないので、java++(仮)とするが、サンプルを考えてみた。
・・・
まずGreetというメッセージをどこにでも書き込むライブラリを題材にする(ログ出力と同じようなもの)。
まずインターフェイスはGreet、メソッドはvoid send(String)とする。つまり、以下の定義でこれは以前と変わりない。
public interface Greet {
void send(String msg);
}
・・・
デフォルトの実装クラス(参照実装)としてGreetStdoutとする。名の通り、System.outにprintlnするだけの実装。これも以前と変わりはない。
package impl;
public class GreetStdout implements Greet {
void send(String msg) {
System.out.println(msg);
}
}
・・・
このインターフェイスを使ってみる。まずは実装依存したコーディングだと、次のように直接実装クラスをimportして、直接newすることになる。
import impl.GreetStdout;
public class Main {
public static void main(String[] args) {
Greet g = new GreetStdout();
g.send("こんちは");
}
}
・・・
もし、インターフェイスを直接newできるのなら、次のコーディングとなる。
public class Main2 {
public static void main(String[] args) {
Greet g = new Greet();
g.send("こんちは");
}
}
・・・
今のコンパイラではMain2はコンパイルできない。いまあるGreet, Main2だけでは、インターフェイスGreetの実装クラスを解決できないからだ。そこでGreetにデフォルトの実装クラスを教えてやる。(#アノテーション自体に自信はないですので・・・)
@ReferenceImplemention impl.GreetStdout
public interface Greet {
void send(String msg);
}
・・・
新しいGreetインターフェイスで定義してあれば、Main2をコンパイルする際にnew Greet()の実装クラスが決定できなくても、とりあえずimpl.GreetStdoutで仮押さえができるので、後はランタイムさんに任せても問題はないはず。
・・・
初期のc++コンパイラはプリプロセッサでcのソースに変換していた(はず)。今回のjava++(仮)も同様にプリプロセッサでjavaのソースに変換できる(はず)。
上記の例ではGreetのファクトリークラスを自動生成してやればいい。またファクトリークラスのデフォルトを、Greetインターフェイスで指定されたアノテーションの情報を利用する。
//it's generated from Greet.java
package generated;
public class GreetFactory {
private static GreetFactory singleton = new GreetFactory();
public static GreetFactory getInstance() { return singleton; }
public Greet newInstance() { return new impl.GreetStdout(); }
}//it's generated from Main2.java
import generated.GreetFactory;
public class Main2 {
public static void main(String[] args) {
Greet g = generated.GreetFactory.getInstance().newInstance();
g.send("こんちは");
}
}
このGreetFactoryではimpl.GreetStdout()をnewしているだけだが、これを設定ファイルを見て動的にインスタンス生成するようにする。当然のことながら、自前で作るのではなく、DIコンテナに委譲してやってよい。
・・・
実装クラスの割り当て(attach)の設定ファイルとしてlog4jが参考になる。つまり、パッケージ名.クラス名の全部または一部で実装クラスをオーバーライドする。次の例ではerror以外のパッケージで使用するとデフォルトのGreetStdout、errorの配下でerror.file以外ならGreetStderr、FatalErrorクラスならGreetEventWriterとなる。
error.*=impl.GreetStderr
error.file.*=impl.GreetFileWriter
error.file.FatalError=impl.GreetEventWriter
まぁ、メソッドまで特定することもできるかな。下記の設定があれば、FatalError.sendsoap()内でnew Greet()するとnew impl.GreetSendSoap()され、FatalError.sendmail()内でnew Greet()するとnew impl.GreetSendMail()されることになる。
error.file.FatalError#sendsoap=impl.GreetSendSoap
error.file.FatalError#sendmail=impl.GreetSendMail
・・・
個々のサンプルのソースをあえて出していないが、error.Main3でも、error.file.Main4でも、error.file.FatalErrorでも、すべてnew Greet();と記述することになる。つまりGreetインターフェイスのMockクラスがデフォルトとして設定されていれば、Greetインターフェイスを利用するメソッドはimportすればすぐに作れる。
これこそEoD(Ease of Development)だと思うんです。
・・・
・・・
先のGreetFactory部分が文字通り固定のファクトリだったので、propertiesファイルを見るファクトリにしてみた。ただしクラス名との完全一致なので、実際に使おうと思うと設定は面倒です。クラス名に一致する最大のキーを探すロジック(一致しなければ'*')が必要。
public class GreetFactory {
private static String PATH = "jp/hiuchida/generated/Greet.properties";
private static Properties props = new Properties();
static {
try {
InputStream is = new FileInputStream(PATH);
props.load(is);
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static GreetFactory singleton = new GreetFactory();
public static GreetFactory getInstance() { return singleton; }
public Greet newInstance() {
return newInstance("*");
}
public Greet newInstance(Class c) {
return newInstance(c.getName());
}
public Greet newInstance(String s) {
try {
String name = props.getProperty(s);
return (Greet)Class.forName(name).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}[Greet.properties]
*=jp.hiuchida.impl.GreetStdout
jp.hiuchida.Main=jp.hiuchida.impl.GreetStderr
・・・
して、むかし作ったSpringの勉強サンプルを見て、GreetFactoryにSpringを乗せてみる。
public class GreetFactory4Spring {
private static String PATH = "jp/hiuchida/generated/Greet.xml";
private static XmlBeanFactory factory = new XmlBeanFactory( (Resource)new ClassPathResource(PATH));
private static GreetFactory4Spring singleton = new GreetFactory4Spring();
public static GreetFactory4Spring getInstance() { return singleton; }
public Greet newInstance() {
return newInstance("*");
}
public Greet newInstance(Class c) {
return newInstance(c.getName());
}
public Greet newInstance(String s) {
return (Greet)factory.getBean(s);
}
}[Greet.xml]
"http://www.springframework.org/dtd/spring-beans.dtd">
・・・
で、プリプロセッサはnewInstance()じゃなくて、newInstance(Class)を生成してもらう。
Greet g;
//REPLACE by pre-processer
//g = new Greet();
g = GreetFactory4Spring.getInstance().newInstance(Main.class);
g.send("こんちは");
・・・
GreetFactory4Springと命名しているけど、実際にはGreetWrapper4Springだな。
単なるファクトリーでしかないが、Greet.xmlはSpringの世界なので別のインスタンスをinjectすることは可能。


