Pure Dependency Injection - Come implementarlo

5

Sto facendo un progetto Android per un corso online. Mi piacerebbe usare DI in quel progetto, così ho iniziato a usare dagger2, ma ora ho iniziato ad avere i tipici problemi da principiante che mi trattengono.

Poiché la scadenza del progetto è in arrivo, decido di abbandonare dagger2 per questo progetto, ma mi piace ancora usare una sorta di DI.

Quindi sono arrivato a questo articolo:

Iniezione di dipendenza pura o Iniezione di dipendenza da uomo povero

Ma non ho capito esattamente come implementarlo in Android. Potrei iniziare a evitare di creare istanze dei miei oggetti nel costruttore o all'interno della classe dipendente, ma da qualche parte deve essere istanziato.

Sto cercando un'idea o una strategia di implementazione per questo "puro DI" in Android. Se possibile, un esempio ben implementato.

    
posta alexpfx 26.07.2017 - 04:38
fonte

2 risposte

7

A questo punto hai probabilmente finito il tuo corso, ma nel caso tu stia ancora cercando, o qualcun altro lo è: il rolling di tuo DI è in realtà molto semplice su Android una volta che sai come farlo.

Creazione del grafico delle dipendenze

Di solito inizio con una classe di applicazione personalizzata (non dimenticarti di registrarla nel file manifest di Android), questa classe vivrà per tutto il ciclo di vita della tua app Android ed è da dove il tuo codice può accedere alle sue dipendenze. Qualcosa del genere:

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 () esplode se qualcosa è nullo, non è necessario usarlo). Le dipendenze effettive sono tutte nella classe ObjectGraph che sostanzialmente assomiglia a questo:

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);
  }

}

Uso

Ora, ovunque ti trovi nella tua app (in un'attività come ad esempio) puoi inserire le tue dipendenze in questo modo:

private Wallet wallet;

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

    wallet = CustomApp.get(Wallet.class);

    //...
}

Puoi usare Dagger in diversi modi, ma l'equivalente di Dagger2 più vicino sarebbe qualcosa del tipo:

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

Dipendenze scopate

Ciò che si sta iniettando qui sarebbe una classe con ambito di livello applicazione . Se desideri solo un oggetto ambito locale che esista solo finché tieni un riferimento ad esso nella tua vista o attività, fai esattamente la stessa cosa ma ciò che inietti è una fabbrica classe:

In ObjectGraph:

WalletFactory walletFactory = new WalletFactory(logger);

Nel tuo frammento, ad esempio:

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

Test

Tutto questo è fatto in modo da poter testare facilmente il codice del tuo livello di vista. Ad esempio, se vuoi eseguire un espresso test, crei l'applicazione, ma prima di mostrare l'attività, sostituisci l'istanza di wallet con un simulatore:

CustomApp.injectMockObject(Wallet.class, mockedWalletWith100Dollars);

Questa è l'istanza del wallet che verrà poi rilevata dal resto del codice durante il test.

In un certo senso questo stile di DI non è flessibile come Dagger2, ma penso che sia molto più chiaro - semplicemente non c'è bisogno di complicare DI ma è in realtà una cosa abbastanza basilare. Questo stile si traduce spesso in piastre di riscaldamento meno rispetto all'utilizzo di un framework DI (una volta incluse le classi di componenti e moduli).

Esempi completi

Ho scritto qualcosa di simile per 5 app di esempio come parte di un framework che ho pubblicato (volendo mantenere gli esempi il più accessibili possibile - non a tutti piace Dagger). Puoi vedere gli esempi completi qui: link

    
risposta data 13.12.2017 - 13:45
fonte
2

Per le semplici classi Java in puro DI di Android viene implementato come al solito con i parametri del costruttore. Ad esempio:

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
    }
}

La complessità di fare puro DI in Android viene fornita con l'integrazione del framework. Come facciamo DI con Activity s quando le attività sono costruite da Android per noi? Non è così semplice, ma la correzione più vicina ai parametri del costruttore è Bundle s che puoi allegare su Intent s. Usiamo un esempio per dimostrare come funzionerebbe:

Dire che ho un'attività UserActivity che recupera un utente da UserService e lo visualizza in una vista. Voglio iniettare il UserService in UserActivity per renderlo più testabile (insieme a tutti gli altri vantaggi di 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"));
    }
}

Poiché Intent implementa Parcelable , possiamo costruire il nostro oggetto grafico alla radice della composizione in questo modo:

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);

Le limitazioni di questo approccio sono che il passaggio degli argomenti si basa molto su Parcelable . Tutte le tue dipendenze devono essere marshallable. Molte classi di Android non implementano (e non dovrebbero) implementare Parcelable , quindi è necessario progettare l'applicazione in modo che non sia necessario trasferirle.

    
risposta data 26.07.2017 - 05:28
fonte

Leggi altre domande sui tag