私は依存性注入に関する Misko Hevery の古典的な 記事を読んでおり、基本的には「オブジェクト グラフ作成コードをコード ロジックから分離する」ことです。
主なアイデアは、「「新しい」演算子を取り除き、それらを専用のオブジェクト (「ファクトリ」) に置き、依存するすべてのものを注入することのようです。」
さて、これを他のいくつかのコンポーネントで構成され、それらのコンポーネントを外界に隔離する仕事をしているオブジェクトで機能させる方法について、私は頭を悩ませているようです。
下手な例
いくつかのフィールドとボタンの組み合わせを表す View クラス。すべてのコンポーネントはグラフィカル UI コンテキストに依存していますが、各サブコンポーネントのインターフェイスの背後に隠したいと考えています。
したがって、次のようなものです(疑似コードでは、言語は実際には問題ではないと思います):
class CustomView() {
public CustomView(UIContext ui) {
this.ui = ui
}
public void start() {
this.field = new Field(this.ui);
this.button = new Button(this.ui, "ClickMe");
this.button.addEventListener(function () {
if (field.getText().isEmtpy()) {
alert("Field should not be empty");
} else {
this.fireValueEntered(this.field.getText());
}
});
}
// The interface of this component is that callers
// subscribe to "addValueEnteredListener"..)
public void addValueEnteredListener(Callback ...) {
}
public void fireValueEnteredListener(text) {
// Would call each listeners in turn
}
}
呼び出し元は次のようにします:
// Assuming a UIContext comes from somewhere...
ui = // Wherever you get UI Context from ?
v = new CustomView(ui);
v.addValueEnteredListener(function (text) {
// whatever...
});
現在、このコードには 3 つの「新しい」演算子があり、Misko (および他の DI 支持者) がどれを削除することを提唱しているか、またはどのように削除するかはわかりません。
new Field() と new Button() を取り除く
注入するだけ
ここでのアイデアは、 Field と Button のインスタンスを実際に注入することではないと思います。これは次の方法で実行できます。
class CustomView() {
public CustomView(Field field, Button button) {
this.field = field;
this.button = button;
}
public void start() {
this.button.addEventListener(function () {
if (field.getText().isEmtpy()) {
alert("Field should not be empty");
} else {
this.fireValueEntered(this.field.getText());
}
});
}
// ... etc ...
これにより、コンポーネントのコードが確実に軽量化され、実際には UI の概念が隠されているため、MetaForm コンポーネントは読みやすさとテストのしやすさの点で明らかに改善されています。
ただし、これらのものを作成する負担はクライアントにあります。
// Assuming a UIContext comes from somewhere...
ui = // wherever ui gets injected from
form = new Form(ui);
button = new Button(ui);
v = new CustomView(form, button);
v.addValueEnteredListener(function (text) {
// whatever...
});
特に、クライアントはクラスのすべての内部を知っているので、ばかげているように聞こえます。
ママは知っている、代わりに彼女に注射する
記事が提唱しているように見えるのは、代わりに Factory を注入してコンポーネント要素を作成することです。
class CustomView() {
public CustomView(Factory factory) {
this.factory = factory;
}
public void start() {
this.field = factory.createField();
this.button = factory.createButton();
this.button.addEventListener(function () {
if (field.getText().isEmtpy()) {
alert("Field should not be empty");
} else {
this.fireValueEntered(this.field.getText());
}
});
}
// ... etc ...
そして、どこかからファクトリを取得する必要があるだけなので、呼び出し元にとってすべてがうまくいきます(そして、このファクトリはUIコンテキストについて知る唯一のものになるので、切り離すことを急いでください. )
// Assuming a UIContext comes from somewhere...
factory = // wherever factory gets injected from
v = new CustomView(factory);
v.addValueEnteredListener(function (text) {
// whatever...
});
考えられる欠点は、MetaForm をテストするときに、通常は「モック」ファクトリを使用する必要があることです... フィールドとボタン クラスのモック バージョンを作成します。しかし、明らかに別の欠点があります...
Yo' Factory so fat!!
工場はどのくらいの大きさになりますか?パターンに厳密に従う場合、実行時にアプリケーションで作成する必要のあるすべてのフリギング コンポーネント (通常は UI の場合です) は、少なくとも 1 つのファクトリで独自の createXXXXX メソッドを取得する必要があります。
今あなたが必要だからです:
- フィールドを作成するための Factory.createField
- ボタンを作成する Factory.createButton
- Factory.createMetaForm を使用して、クライアントがフィールド、ボタン、および MetaForm を作成します (たとえば、MetaEditPage がいずれかを使用したい場合)。
- そして明らかに、クライアントの Factory.createMetaEditPage ..
- ...そしてそのカメはずっと。
これを単純化するためのいくつかの戦略を見ることができます:
- 可能な限り、「起動」時に作成されるグラフの部分を実行時に作成される部分から分離します (前者には Spring のような DI フレームワークを使用し、後者にはファクトリを使用します)。
- ファクトリを階層化するか、関連するオブジェクトを同じファクトリに配置します (UIWidgetFactory は Field と Button に適していますが、他のものはどこに配置しますか? アプリケーションにリンクされた Factory に? 他の論理レベルに?)
ApplicationProcessFactoryCreator.createFactory().createProcess().startApplication() ナンセンス チェーンを呼び出さずに Java アプリは何もできないという C 関係者のジョークをほとんど聞くことができます...
だから私の質問は:
- ここでポイントが完全に欠けていますか?
- そうでない場合、物事を耐えられるものにするためにどの戦略を提案しますか?
補遺:依存性注入が役立つかどうかわからない理由
guice のようなフレームワークで依存性注入を使用することにしたとします。私は次のようなコードを書くことになります:
class CustomView
@Inject
private Field fiedl;
@Inject
private Button button;
public void start() {
this.button.addEventListener(....
// etc...
そして、何?
私の「構成ルート」はそれをどのように利用しますか? Field と Button の「シングルトン」(「クラスの単一インスタンス」のように小文字の「s」を使用)を構成することはできません( MetaForm のインスタンスと同じ数のインスタンスを作成したいためですか?
私の問題は作成したいボタンのインスタンスではなく、このフォームでのみ意味のある構成(テキストなど)で最近作成したいだけなので、プロバイダーを使用するのは意味がありません.
私は、依存関係ではなくコンポーネントの一部を新規作成しているため、DI は役に立ちません。そして、サブコンポーネントを依存関係に変えて、フレームワークにそれらを注入させることができると思います。この場合、サブコンポーネントを注入することは本当に人工的で直観的に見えます...だから、何かが欠けているに違いありません;)
編集
特に、私の問題は、次のシナリオをテストする方法を理解できないように見えることです。
「ボタンをクリックしたときにフィールドが空の場合、エラーが発生するはずです」。
これは、ボタンを挿入して「fireClicked」イベントを手動で呼び出せば実行可能ですが、少しばかげているように感じます。
別の方法は、単に view.getButton().fireClicked() を実行することですが、それは少し見苦しく見えます...