フィールドベース?

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の)インターフェースを用意するより、直感的で柔軟だと思う。

問題点としては…

  1. 多分、遅い。
  2. コードが邪悪。
  3. IDEの補完機能が使えない。
  4. 抽象メソッドのように実装を強制できない。

コードがどれくらい遅いかは不明。でもそれほど問題にならないような気がする。
3.と4.はちょっと問題だけど、各コンポーネントで入出力のインターフェースが共通なら、親クラスを作ればよいと思う。でも、それだとフィールドがprivateじゃなくてprotectedになるなぁ…

Eclipseのプロジェクト一式はここ

追記
setField()、hasField()はコンポーネント側に持たせたほうがオブジェクト指向っぽい。AbstractCompornentクラスを作るとよいかも。
追記2
Field#set()は自動的にキャストしてくれてるっぽい。当たり前か…