React Native - Sta usando un singleton la migliore alternativa a DI?

8

Ho letto molto sul pattern di singleton e su come è "cattivo" perché rende le classi difficili da testare, quindi dovrebbe essere evitato. Ho letto alcuni articoli che spiegano come il singleton possa essere sostituito con l'iniezione di dipendenza, ma mi sembra inutilmente complesso.

Ecco il mio problema in modo un po 'più dettagliato. Sto creando un'app mobile utilizzando React Native e voglio creare un client REST che comunichi con il server, recuperi dati, carichi i dati e gestisca l'accesso (memorizza il token di accesso e invialo a ogni richiesta dopo l'accesso).

Il mio piano iniziale era di creare un oggetto singleton (RESTClient) che la mia app usasse inizialmente per accedere e quindi fare richiesta inviando le credenziali dove necessario. L'approccio di DI sembra davvero complicato per me (forse perché non ho mai usato DI prima) ma sto usando questo progetto per imparare il più possibile, quindi voglio fare il meglio qui. Qualsiasi suggerimento e commento sono molto apprezzati.

Modifica: Ora ho realizzato che ho formulato la mia domanda in modo insufficiente. Volevo delle indicazioni su come evitare il pattern singleton in RN e dovrei farlo anche io. Fortunatamente Samuel mi ha dato il tipo di risposta che volevo. Il mio problema era che volevo evitare il pattern singleton e usare DI, ma sembrava davvero complicato implementarlo in React Native. Ho fatto ulteriori ricerche e l'ho implementato usando il sistema di contesto Reacts.

Per chiunque sia interessato ecco come l'ho fatto. Come ho detto, ho usato il contesto in RN che è qualcosa di simile a oggetti di scena ma è propagato a ogni componente.

Nel componente root fornisco le dipendenze necessarie come questa:

export default class Root extends Component {
  getChildContext() {
    restClient: new MyRestClient();
  }

  render() {...}
}
Root.childContextTypes = {restClient: PropTypes.object};

Ora restClient è disponibile in tutti i componenti di seguito Root. Posso accedervi in questo modo.

export default class Child extends Component {
  useRestClient() {
    this.context.restClient.getData(...);
  }

  render() {...}
}
Child.contextTypes = {restClient: PropTypes.object}

Questo sposta efficacemente la creazione dell'oggetto dalla logica e disaccoppia l'implementazione del client REST dai miei componenti.

    
posta Mateo Hrastnik 13.07.2017 - 20:30
fonte

3 risposte

15

L'iniezione di dipendenza non ha bisogno di essere affatto complessa, ed è assolutamente degno di essere appresa e utilizzata. Di solito è complicato dall'uso di framework di dipendenze, ma non sono necessari.

Nella sua forma più semplice, l'iniezione delle dipendenze è che passa le dipendenze invece di importarle o costruirle. Questo può essere impiantato semplicemente usando un parametro per ciò che verrebbe importato. Diciamo che hai un componente chiamato MyList che deve usare RESTClient per recuperare alcuni dati e visualizzarli all'utente. L'approccio "singleton" sarebbe simile a questo:

import restClient from '...' // import singleton

class MyList extends React.Component {
  // use restClient and render stuff
}

Questo coppia strettamente MyList a restClient , e non c'è modo di testare unit MyList senza testare restClient . L'approccio DI sarebbe simile a questo:

function MyListFactory(restClient) {
  class MyList extends React.Component {
    // use restClient and render stuff
  }

  return MyList
}

Questo è tutto ciò che serve per usare DI. Aggiunge al massimo due righe di codice e puoi eliminare un'importazione. Il motivo per cui ho introdotto una nuova funzione "factory" è perché AFAIK non è possibile passare parametri aggiuntivi del costruttore in React, e preferisco non passare queste cose attraverso le proprietà React perché non è portabile e tutti i componenti genitore devono sapere per passare tutto oggetti di scena per i bambini.

Quindi ora hai una funzione per costruire MyList componenti, ma come la usi? Il modello DI fa scoppiare la catena delle dipendenze. Supponiamo che tu abbia un componente MyApp che utilizza MyList . L'approccio "singleton" sarebbe:

import MyList from '...'
class MyApp extends React.Component {
  render() {
    return <MyList />
  }
}

L'approccio DI è:

function MyAppFactory(ListComponent) {
  class MyApp extends React.Component {
    render() {
      return <ListComponent />
    }
  }

  return MyApp
}

Ora possiamo testare MyApp senza testare MyList direttamente. Potremmo anche riutilizzare MyApp con un tipo di elenco completamente diverso. Questo schema mostra la radice della composizione . Qui è dove chiamate le vostre fabbriche e cablate tutti i componenti.

import RESTClient from '...'
import MyListFactory from '...'
import MyAppFactory from '...'

const restClient = new RESTClient(...)
const MyList = MyListFactory(restClient)
const MyApp = MyAppFactory(MyList)

ReactDOM.render(<MyApp />, document.getElementById('app'))

Ora il nostro sistema utilizza una singola istanza di RESTClient , ma l'abbiamo progettata in modo che i componenti siano accoppiati liberamente e facili da testare.

    
risposta data 13.07.2017 - 22:33
fonte
5

Secondo le tue premesse (apprendimento), la risposta più semplice è no, i singleton non sono la migliore alternativa all'iniezione di dipendenza .

Se l'apprendimento è l'obiettivo, troverai DI essere una risorsa più preziosa nella di Singleton. Potrebbe sembrare complesso, ma la curva di apprendimento è quasi su tutto ciò che devi imparare da zero. Non sdraiarsi nella tua zona di comfort o non ci sarà affatto apprendimento.

Tecnicamente, c'è poca differenza tra singleton e singola istanza (cosa penso che tu stia cercando di fare). Imparando DI ti renderai conto che puoi iniettare singole istanze su tutto il codice. Troverete il vostro codice per essere più facile da testare e strettamente accoppiato. ( Per maggiori dettagli consulta la risposta di Samuel )

Ma non ti fermi qui. Implementare lo stesso codice con Singleton. Quindi confronta entrambi gli approcci.

Comprendere e avere familiarità con entrambe le implementazioni ti permetterà di sapere quando sono appropriati e probabilmente sarai in grado di rispondere a te stesso alla domanda.

È ora, durante l'allenamento, quando crei il tuo Golden Hammers , quindi se decidi di evitare l'apprendimento di DI è probabile che finirai per implementare singleton ogni volta che ne avrai bisogno.

    
risposta data 13.07.2017 - 22:42
fonte
0

Sono d'accordo con le altre risposte sul fatto che imparare cos'è DI e come usarlo è una buona idea.

Detto questo, l'avvertimento che i singleton fanno troppo duro il test viene di solito fatto da persone che usano linguaggi tipizzati staticamente (C ++, C #, Java, ecc.).

Al contrario in un linguaggio dinamico (Javascript, PHP, Python, Ruby, ecc.) , di solito non è più difficile sostituire un singleton con un'implementazione specifica del test di quanto lo sarebbe nel caso in cui stanno usando DI.

In questo caso, consiglio di utilizzare il design che sembra più naturale a te e ai tuoi co-sviluppatori, perché ciò tende ad evitare errori. Se questo risulta in singoletti, così sia.

(Ma poi ancora: impara DI prima di prendere questa decisione.)

    
risposta data 15.07.2017 - 16:11
fonte