Modello di progettazione del repository in Swift

Un modo pulito per interrogare i tuoi modelli

Che problema risolve?

Se è necessario eseguire più volte query sugli oggetti del modello da posizioni diverse nel codice, un repository può essere davvero utile per fornire un punto di accesso singolo per lavorare con i modelli e rimuovere il codice di query duplicato. Puoi prenderlo ancora di più e usarlo con i protocolli, in questo modo puoi facilmente cambiare le implementazioni (ad esempio per i test unitari) o puoi usarlo con generici per creare un'astrazione più generica * rullo di tamburi *. In questo articolo tratterò tutti questi casi.

Disegnando la scena.

Supponiamo che tu abbia del codice che recupera i dati da un'API e li mappa per modellare gli oggetti. In questo esempio prenderò un elenco di articoli da un server.

Questo può sembrare un po 'strano, ma è solo RxSwift, usando Moya come livello di astrazione della rete, ma non importa davvero cosa sta succedendo. Il modo in cui recuperi i tuoi dati dipende totalmente da te.

Questo pezzo di codice lo fa

  1. Una richiesta GET al server
  2. Associa il JSON restituito a una matrice di oggetti Article
  3. La chiusura viene chiamata quando tutto il lavoro è finito.

Perché abbiamo bisogno di un repository?

Bene, al momento non lo facciamo. Se si chiama l'API solo una volta nell'intera base di codice, l'aggiunta di un repository potrebbe essere eccessiva (o come alcuni potrebbero dire sovraingegneria).

Ok ... ma quando è conveniente usare un oggetto repository?
Supponiamo che il tuo codebase inizi a crescere e devi scrivere il codice per recuperare gli articoli più e più volte. Potresti dire "copiamo il codice e incolliamolo ovunque sia necessario per recuperare tutti gli articoli".

Nessun danno fatto, nessuno è morto. Destra?

In quel momento un grande allarme rosso dovrebbe iniziare a lampeggiare nel tuo cervello.

Ciao repository.

Un repository è solo un oggetto che incapsula tutto il codice per interrogare i tuoi modelli in un unico posto, quindi hai un singolo punto di ingresso se vuoi ad es. prendi tutti gli articoli.

Creiamo un oggetto repository che fornisce un'API pubblica per ottenere gli articoli.

Ora possiamo chiamare questo metodo e non dobbiamo preoccuparci di ciò che accade dietro le quinte per ottenere gli articoli reali.
Basta chiamare il metodo e otterrai gli articoli. Bello vero?
Ma aspetta, c'è di più!

Gestisci tutte le interazioni con gli articoli

Possiamo usare il repository per aggiungere più metodi per interagire con il nostro oggetto modello. La maggior parte delle volte si desidera eseguire operazioni CRUD (creare, leggere, aggiornare, eliminare) sul proprio modello. Bene, basta aggiungere la logica per queste operazioni nel repository.

Questo rende una bella API da utilizzare in tutto il codice, senza dover ripetere lo stesso codice più volte.

In pratica, l'uso di un repository sarebbe simile a questo.

Abbastanza carino e leggibile, vero? Ma aspetta, diventa ancora meglio.

Accensione: protocolli

Nel codice precedente, ho sempre usato l'esempio di "ottenere dati da un'API". Ma cosa succede se è necessario aggiungere il supporto per caricare i dati da un file JSON locale anziché da una fonte online.

Bene, se si crea un protocollo che elenca i nomi dei metodi, è possibile creare un'implementazione per l'API online e una per mettere offline i dati.

Questo potrebbe assomigliare a questo.

Un protocollo dice semplicemente "se sei conforme a me, devi avere queste firme sui metodi, ma non mi interessa l'implementazione effettiva!"

Quindi è fantastico, puoi creare un WebArticleRepository e un LocalArticleRepository. Entrambi avranno tutti i metodi elencati nel protocollo, ma puoi scrivere 2 implementazioni totalmente diverse.

Accensione: unit test

L'uso dei protocolli è anche molto comodo quando si desidera testare l'unità del codice, poiché è possibile creare un altro oggetto che implementa il protocollo del repository, ma restituisce invece dati fittizi.

Se lo usi insieme all'iniezione di dipendenza, è davvero facile testare un oggetto specifico.

Un esempio

Supponiamo che tu abbia un modello di visualizzazione e che il modello di visualizzazione ottenga i suoi dati tramite un repository.

Se desideri testare il modello di visualizzazione, sei bloccato con gli articoli che verranno recuperati dal Web.
Questo non è in realtà ciò che vogliamo. Vogliamo che il nostro test sia il più deterministico possibile. In questo caso, gli articoli recuperati dal Web potrebbero cambiare nel tempo, non potrebbe esserci alcuna connessione Internet al momento dell'esecuzione dei test, il server potrebbe essere inattivo, ... questi sono tutti scenari possibili in cui i nostri test fallirebbero, perché sono fuori dal nostro controllo. E quando testiamo, vogliamo / dobbiamo avere il controllo.

Fortunatamente, in realtà è molto semplice risolverlo.

Ciao, iniezione di dipendenza.

Devi solo impostare la proprietà articleRepo tramite l'inizializzatore. Il caso predefinito sarà quello desiderato per il codice di produzione e quando si scrive un test unitario, è possibile sostituire il repository con la propria versione simulata.

Ma forse stai pensando, e che dire dei tipi? Un WebArticleRepository non è un MockArticleRepository, quindi il compilatore non si lamenterà? Bene, non se usi il protocollo come tipo. In questo modo informiamo il compilatore, consentiamo tutto purché sia ​​conforme al protocollo ArticleRepository (che fanno sia il Web che MockArticleRepository).

Il codice finale sarebbe simile a questo.

E nel tuo unit test potresti scambiarlo in questo modo.

Ora hai il pieno controllo su quali dati restituisce il tuo repository.

Super power-up: generici

Potresti andare oltre, usando i generici. Se ci pensate, la maggior parte dei repository ha sempre le stesse operazioni

  1. prendi tutte le cose
  2. prendi alcune delle cose
  3. inserire alcune cose
  4. elimina cosa
  5. aggiorna una cosa

Bene, l'unica cosa diversa è la parola "cosa", quindi potrebbe essere un ottimo candidato per usare un protocollo con i generici. Potrebbe sembrare complicato, ma in realtà è abbastanza semplice da fare.

Innanzitutto rinomineremo il protocollo in Repository, per renderlo più ... generico .
E poi rimuoveremo tutti i tipi di articolo e li sostituiremo con la magia T. Ma la lettera T è solo un rimpiazzo per ... tutto ciò che vogliamo che sia. Dobbiamo solo contrassegnare T come tipo associato del protocollo.

Quindi ora possiamo usare questo protocollo per qualsiasi oggetto modello che abbiamo.

1. Archivio degli articoli

Il compilatore dedurrà il tipo di T nell'articolo, perché implementando i metodi, abbiamo specificato che cos'è T. In questo caso un oggetto articolo.

2. Repository utente

Questo è tutto.

Spero che l'articolo ti sia piaciuto e, se hai domande o commenti, chiedi loro di seguito o contattami su Twitter e facciamo una chat.