The Cucumber for Java BookでCucumberを勉強しているのですが、PicoContainerでインジェクトするオブジェクトを引数ありのコンストラクタで生成する方法が書いていなかったので、調べました。(実は、最新のActiveJDBC(1.4.13)では本のサンプルが動かなかった(本と同じバージョンの1.4.1では動作した)ので、代替手段を必要だったというのが、最初のきっかけです。)
やりたいこと
上記の本のサンプルを一部改造したものですが、AccountオブジェクトをStep Definitionにcumcumber-picocontainerでDIしたいのですが、Accountオブジェクトを生成する際にコンストラクタで引数を指定する必要があるのです。
public class Account extends Model {
public Account() {
}
public Account(int number) {
setInteger("number", number);
setString("balance", "0.00");
saveIt();
}
...
public class AccountSteps {
private Account account;
public AccountSteps(Account account) {
this.account = account;
}
...
本では、引数付きのコンストラクタを直接使用せずに、下記のようにに子クラスを作ってそれをDIすることによって、同じことを実現しているのですが、
public class TestAccount extends Account {
public TestAccount() {
super(1234);
}
...
public class AccountSteps {
private TestAccount account;
public AccountSteps(TestAccount account) {
this.account = account;
}
...
そんなことをせずに引数付きのコンストラクタで生成したAccountオブジェクトを直接DIするにはどうしたらいいのでしょうか?
PicoContainerを直接使用すれば
Cucumberからではなくて、直接PicoContainerを使用する場合は、DIするオブジェクトを自分で生成する、もしくは引数付きのコンストラクタを使用してオブジェクトを生成するように設定することが可能です。
MutablePicoContainer pico = new DefaultPicoContainer();
pico.addComponent(new Account(5)); // 引数付きのコンストラクタでAccountオブジェクトを生成
// もしくは、コンストラクタの引数を指定
pico.addComponent(Account.class, Account.class, new Parameter[] { new ConstantParameter(new Integer(5)) });
Account account = pico.getComponent(Account.class);
System.out.println("number = " + account.getNumber());
Cucumberから実行した場合は、MutablePicoContainerはCumcumberが生成して、DIするコンポーネントの設定もしてくれるので、通常使う側は何にもする必要はなくて楽でいいのですが、逆に自分で設定したい場合は、MutablePicoContainerをCucumberから取得する手段がないため、どうすることもできません。
Cucumberでもある程度はDIコンテナの設定が可能だが。。。
同じような要求(Ability to configure PicoContainer)は当然あって、これに応える形でObjectFactory APIが使えるようになったのですが、このAPIを使ってできるのは、
pico.addComponent(Account.class)
に相当することだけで(これができるだけでも、Interfaceに対してどの実装クラスを使うかを指定できるようになるので意味があることですが)、
pico.addComponent(new Account(5));
pico.addComponent(Account.class, Account.class, new Parameter[] { new ConstantParameter(new Integer(5)) });
のように、インスタンスを登録したり、コンストラクタの引数を指定するようなことは現状ではできないようです。
では、どうすれば?
実は、もし引数が今回の例のようにintではなくて通常のクラスであったら、普通にできるのです。DI初心者の私はこれに気づくまでに時間がかかりました。cucumber-picocontainerはお利口なので、何も言わなくてもコンストラクタの引数のオブジェクトも勝手にDIしてくれるのです。なので、ちょっと汚い実装になってしまいますが、コンストラクタの引数用のクラス(AccountNumber)を作ってしまえば、やりたいことは実現できるのでした。
public class Account extends Model {
public Account() {
}
public Account(int number) {
setInteger("number", number);
setString("balance", "0.00");
saveIt();
}
// DI用のコンストラクタ
public Account(AccountNumber number) {
this(number.getNumber());
}
...
public class AccountNumber {
private int number;
public AccountNumber() {
number = 1234;
}
public int getNumber() {
return number;
}
}
これで、Step Definitionは何も変えずに、引数を使って生成したAccountオブジェクトを渡すことができるようになりました。
public class AccountSteps {
private Account account;
public AccountSteps(Account account) {
this.account = account;
}
...