web-dev-qa-db-ja.com

純粋な依存性注入-実装方法

私はオンラインコースでAndroid=プロジェクトを行っています。そのプロジェクトでDIを使用したいので、dagger2を使い始めましたが、今、典型的な初心者の問題が発生し始めています。戻ってきた。

プロジェクトの期限が近づいているので、このプロジェクトではdagger2を放棄することにしましたが、それでも何らかのDIを使用したいです。

だから私はこの記事に来ました:

純粋な依存性注入または貧しい人の依存性注入

しかし、私はこれをAndroidに実装する方法を正確に理解していませんでした。コンストラクターまたは依存クラス内でオブジェクトのインスタンスを作成しないようにすることもできますが、どこかでインスタンス化する必要があります。

Androidでのこの「純粋なDI」のアイデアまたは実装戦略を探しています。可能であれば、適切に実装された例。

7
alexpfx

あなたはおそらくこれまでにコースを終了しましたが、まだ検索している場合、または他の誰かがそうである場合:独自のDIをローリングすることは、実際にAndroidでそれを行う方法を知っていれば非常に簡単です。

ディペンデンシーグラフの作成

私は通常、最初にカスタムアプリケーションクラスから始めます(Androidマニフェストに登録することを忘れないでください)。このクラスは、Android =アプリであり、コードがその依存関係にアクセスできる場所です。次のようなもの:

public class CustomApp extends Application {

  private static ObjectGraph objectGraph;


  @Override
  public void onCreate() {
    super.onCreate();

    objectGraph = new ObjectGraph(this);
  }


  // This is where your code accesses its dependencies
  public static <T> T get(Class<T> s) {
    Affirm.notNull(objectGraph);
    return objectGraph.get(s);
  }


  // This is how you inject mock dependencies when running tests
  public <T> void injectMockObject(Class<T> clazz, T object) {
    objectGraph.putMock(clazz, object);
  }

}

(Affirm.notNull()は、何かがnullの場合に爆発するだけで、使用する必要はありません)。実際の依存関係はすべて、基本的に次のようなObjectGraphクラスにあります。

class ObjectGraph {

  private final Map<Class<?>, Object> dependencies = new HashMap<>();

  public ObjectGraph(Application application) {

    // Step 1.  create dependency graph
    AndroidLogger logger = new AndroidLogger();
    Wallet wallet = new Wallet(logger);

    //... this list can get very long


    // Step 2. add models to a dependencies map if you will need them later
    dependencies.put(Wallet.class, wallet);

  }

  <T> T get(Class<T> model) {

    Affirm.notNull(model);
    T t = model.cast(dependencies.get(model));
    Affirm.notNull(t);

    return t;
  }

  <T> void putMock(Class<T> clazz, T object) {

    Affirm.notNull(clazz);
    Affirm.notNull(object);

    dependencies.put(clazz, object);
  }

}

使用法

これで、アプリ内のどこにいても(たとえば、アクティビティで)、次のように依存関係を注入できます。

private Wallet wallet;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    wallet = CustomApp.get(Wallet.class);

    //...
}

Daggerはいくつかの方法で使用できますが、最も近いDagger2の同等物は次のようになります。

AppComponent appComponent = CustomApp.getAppComponent();
Wallet wallet  = appComponent.getWallet();

スコープ付き依存関係

ここで注入するのは、applicationレベルのスコープを持つクラスです。 ローカルスコープオブジェクトが必要な場合は、ビューまたはアクティビティでそれへの参照を保持している間のみ存在し、まったく同じことを行いますあなたが注入するものはfactoryクラスです:

ObjectGraph内:

WalletFactory walletFactory = new WalletFactory(logger);

例えばあなたのフラグメントで:

Wallet wallet = CustomApp.get(WalletFactory.class).getNewWallet();

テスト中

これはすべて、ビューレイヤーコードを簡単にテストできるようにするために行われます。たとえば、エスプレッソテストを実行する場合は、アプリケーションを作成しますが、アクティビティを表示する前に、ウォレットインスタンスをモックに置き換えます。

CustomApp.injectMockObject(Wallet.class, mockedWalletWith100Dollars);

これは、テスト中に残りのコードによって取得されるウォレットインスタンスです。

ある意味では、このDIのスタイルはDagger2ほど柔軟ではありませんが、私はそれがはるかに明確であると思います。DIを複雑にする必要がないだけで、実際には非常に基本的なものです。このスタイルは、多くの場合、DIフレームワークを使用するよりもlessボイラープレートになります(コンポーネントおよびモジュールクラスを含めた後)。

完全な例

私が公開したフレームワークの一部として、5つのサンプルアプリ用に同様のものを書きました(サンプルをできるだけ広くアクセスできるようにしたい-誰もがDaggerを好むわけではない)。ここで完全な例を見ることができます: https://github.com/erdo/asaf-project/blob/master/example01databinding/src/main/Java/foo/bar/example/asafdatabinding/ObjectGraph.Java

8
asaf android

単純なJavaのクラスAndroidのクラスの場合、純粋なDIは、コンストラクタパラメータを使用して通常どおり実装されます。たとえば、次のようになります。

interface UserService extends Parcelable {
    User getUser(String name);
}

class DbUserService implements UserService {
    private final DbHelper db;

    public RestUserService(DbHelper db) {
        this.db = db;
    }

    User getUser(String name) {
        // use db to get user by name
    }
}

Android=で純粋なDIを行うことの複雑さは、フレームワークの統合に付属しています。アクティビティがAndroidで構築されている場合に、ActivitysでDIを行うには簡単ではありませんが、コンストラクタパラメータに最も近い相関はBundlesであり、これを Intent sに付加できます。例を使用して、うまくいくでしょう:

たとえば、UserActivityからユーザーをフェッチしてビューに表示するアクティビティUserServiceがあるとします。 UserServiceUserActivityに注入して、よりテストしやすくします(DIの他のすべての利点と共に)。

class UserActivity extends Activity {
    private Optional<UserService> userService = Optional.empty();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // get UserService from bundle
        userService = Optional.of(getIntent().getExtras().getParcelable<UserService>("userService"));
        setContentView(R.layout.activity_user);
    }

    @Override
    protected void onResume() {
        super.onResume();
        userService.map(us -> us.getUser("Bill"));
    }
}

IntentParcelableを実装しているため、次のように合成ルートでオブジェクトグラフを作成できます。

UserService userService = new DbUserService(...);
Intent userActivityIntent = new Intent(this, UserActivity.class);
userActivity.putExtra("userService", userService);
Intent mainIntent = new Intent(this, MainActivity.class);
mainIntent.putExtra("userActivityIntent", userActivityIntent);
startActivity(mainIntent);

このアプローチの制限は、引数を渡すことがParcelableに大きく依存することです。すべての依存関係はマーシャリング可能である必要があります。多くのAndroidのクラスはParcelableを実装していない(してはいけない)ので、それらを渡す必要がないようにアプリケーションを設計する必要があります。

3
Samuel