コードベースを依存性注入コンテナに徐々に移動する


9

多くの「アンチパターン」シングルトンを備えた大規模なコードベース、静的メソッドを含むユーティリティクラス、およびnewキーワードを使用して独自の依存関係を作成するクラスがあります。コードのテストが非常に困難になります。

依存性注入コンテナ(プロジェクトのGuice場合はGWT)にコードを徐々に移行したいと考えています。依存性注入についての私の理解から、それは全部かゼロかです。すべてのクラスは、Spring / Guiceによって管理されるか、まったく管理されません。コードベースが大きいので、コードを一晩で変換できません。ですから、徐々にそれを行う方法が必要です。

問題は、他のクラスに注入する必要のあるクラスから始めると、@Injectそれらのクラスがまだコンテナーによって管理されていないため、それらのクラスでシンプルを使用できないことです。したがって、これはどこにも挿入されない「トップ」クラスまでの長いチェーンを作成します。

私が見る唯一の方法は、Injector/アプリケーションコンテキストをシングルトンを通じてグローバルに利用できるようにすることです。これにより、他のクラスがそこからマネージドBeanを取得できるようになります。しかし、それcomposition rootはアプリケーションに明らかにしないという重要な考えと矛盾します。

もう1つのアプローチはボトムアップです。「高レベル」クラスから始めて、依存関係注入コンテナーにそれらを含め、ゆっくりと「小さい」クラスに移動します。しかし、まだグローバル/スタティックに依存しているこれらの小さなクラスをテストできるので、私は長い間待たなければなりません。

このような段階的な移行を実現する方法は何でしょうか?

PS 依存性注入への段階的アプローチという質問はタイトルは似ていますが、私の質問には答えません。


1
ハード結合されたクラスから依存関係注入コンテナーを使用することに直接移行しますか?依存関係注入フレームワークまたはツールとの結合を考える前に、インターフェイスを使用して、クラスを分離するためのリファクタリングを最初に行う必要があります。
TulainsCórdova16年

けっこうだ。したがって、他の30のクラス(そのうちの1つはクラスB)で使用されるユーティリティクラスAがあり、テストできなくなります。クラスAをクラスBのコンストラクター依存関係にすることを考えました。コンストラクターへのすべての呼び出しを変更し、Aを内部に渡す必要があります。しかし、どこから入手できますか?
damluar 2016

開始するためにDIを適用できる最小単位がBである場合、当面の間、Bを構築しているところはどこでも、Aを構築しても害はありません-Aに依存関係がない場合。ボールが回転したら、戻ってそれをリファクタリングできます。
アンディハント

@damluarそもそもファクトリー?
TulainsCórdova2016

1
@damluarユーティリティクラスに依存しているからといって、クラスがテスト不能になることはありません。それはあなたのユニットがあなたがそれが望むよりも少し大きいことを意味します。ユーティリティクラスを単独でテストできる場合は、他のすべての30クラスのテストで問題なく使用できます。ユニットテストに関するMartin Fowlersブログを読んでください。
gbjbaanb 16

回答:


2

C#私の選択した言語で申し訳ありませんが、私は読むことJavaができますが、おそらくそれを書こうとする構文を破壊します...同じ概念がとの間C#Javaも当てはまります。テスト可能。

与えられた:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

IOCコンテナーを使用せずに、DIを使用するように簡単にリファクタリングできます。さらに、いくつかの手順に分解することもできます。

(潜在)ステップ1-依存関係を取り込みますが、呼び出し(UI)コードに変更はありません。

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

リファクタリング2(IOCコンテナを実装して呼び出しコードをすぐに変更する場合は最初のリファクタリング):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

ステップ2は技術的には単独で行うこともできますが、(潜在的に)はるかに多くの作業になる可能性があります-DIに求めている機能を現在「更新」しているクラスの数によって異なります。

ステップ1->ステップ2のルートを使用することを検討してください-とはFoo無関係に、の単体テストを作成できますBar。一方、ステップ1のリファクタリングの前は、両方のクラスの実際の実装を使用せずに簡単に達成することはできませんでした。ステップ1->ステップ2(すぐにステップ2ではなく)を実行すると、時間の経過に伴う小さな変更が可能になり、リファクタリングが問題なく機能することを確実にするために、テストハーネスを開始できます。


0

概念は、Java、PHP、またはC#を使用していても同じです。この質問は、このYouTubeビデオでGemma Anibleによってかなりうまく処理されています。

https://www.youtube.com/watch?v=Jccq_Ti8Lck(PHP、ごめんなさい!)

テスト不可能なコードを、新しいテスト可能なコードを呼び出す "ファサード"(より適切な用語がないため)に置き換えます。その後、徐々に古いコールを注入されたサービスに置き換えることができます。私は過去にこれをやったことがあり、それはかなりうまくいきます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.