IoCパターンだと、Setterベースにしろ、コンストラクタベースにしろ、フィールドに値をセットするメソッドと、値を保持するフィールドが必要になる。
フィールド毎にsetterを用意するのもめんどくさいなぁ…でも、publicなフィールドは危険だよなぁ…と思ったので、privateなフィールドに直接、値をセットする方法を考えてみた。
コンポーネントを呼び出す側はこんな感じ。
コンポーネントのインターフェースはこんな感じ。
package foo.bar;import java.lang.reflect.Field;
public class ViciousManager {
public static void main(String[] args) {
try {
ViciousManager manager = new ViciousManager();ViciousCompornent foo = manager.getCompornent("foo.bar.impl.Foo");
ViciousCompornent bar = manager.getCompornent("foo.bar.impl.Bar");foo.execute();
bar.execute();
} catch (Exception e) {
e.printStackTrace();
}
}public ViciousCompornent getCompornent(String className) {
try {
Class clazz = Class.forName(className);
ViciousCompornent compornent = (ViciousCompornent) clazz
.newInstance();// 変数fooをセット。
if (hasField(compornent, "foo")) {
setField(compornent, "foo", "this is foo.");
}// 変数barをセット。
if (hasField(compornent, "bar")) {
setField(compornent, "bar", "this is bar.");
}return compornent;
} catch (Exception e) {
throw new RuntimeException(e);
}
}private boolean hasField(ViciousCompornent compornent, String name) {
boolean ret = true;try {
compornent.getClass().getDeclaredField(name);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
ret = false;
}return ret;
}private void setField(ViciousCompornent compornent, String name, Object val) {
try {
Field field = compornent.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(compornent, val);
field.setAccessible(false);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}}
各コンポーネントはこんな感じ。
package foo.bar;public interface ViciousCompornent {
public void execute();
}
package foo.bar.impl;import foo.bar.ViciousCompornent;
public class Foo implements ViciousCompornent {
private String foo = null;
public void execute() {
System.out.println("foo: " + foo);
}}
で、実行結果は以下の通り。
package foo.bar.impl;import foo.bar.ViciousCompornent;
public class Bar implements ViciousCompornent {
private String bar = null;
public void execute() {
System.out.println("bar: " + bar);
}}
foo: this is foo.
bar: this is bar.
この方法だと、各コンポーネントを開発者に実装してもらうときに「データベースのコネクションが必要なコンポーネントは、フィールド conn を実装してください」などと周知することができる。
setterを実装してもらったり、異なる(Javaの)インターフェースを用意するより、直感的で柔軟だと思う。
問題点としては…
- 多分、遅い。
- コードが邪悪。
- IDEの補完機能が使えない。
- 抽象メソッドのように実装を強制できない。
コードがどれくらい遅いかは不明。でもそれほど問題にならないような気がする。
3.と4.はちょっと問題だけど、各コンポーネントで入出力のインターフェースが共通なら、親クラスを作ればよいと思う。でも、それだとフィールドがprivateじゃなくてprotectedになるなぁ…
- 追記2
- Field#set()は自動的にキャストしてくれてるっぽい。当たり前か…