pattern - viper architecture ios




Usa Case con 2 modi per la stessa azione (3)

Domanda 1: Qual è il modo corretto di costruire un caso d'uso (o più di uno) con 2 modi per fare la stessa azione?

Per esempio:

Ho 3 schermi in un'app per iOS:
1. Una vista mappa, che può essere "premuto a lungo" e ha un pulsante della fotocamera.
2. Una vista videocamera, che viene visualizzata se l'utente tocca il pulsante della fotocamera nella visualizzazione mappa.
3. Una vista di modifica posto / pin, che viene visualizzata se l'utente "preme a lungo" la vista della mappa, o dopo che l'utente ha scelto una foto nella vista della telecamera. Questa vista di modifica ha un pulsante di salvataggio per creare effettivamente il luogo con una foto e la posizione (coordinata a pressione lunga o posizione corrente nel caso in cui il pulsante della fotocamera fosse premuto).

Titolo: Crea luogo flusso di base:
1. Utente "pressione lunga" sulla mappa.
2. App rilascia un pin temporaneo e visualizza la vista di modifica del luogo.
3. L'utente modifica le informazioni sul luogo e preme il pulsante Salva.
4. L'app crea il luogo e lo salva.

Titolo: Crea luogo flusso di base:
1. L'utente preme il pulsante più.
2. L'app visualizza la vista della telecamera.
3. L'utente scatta una foto.
4. App crea luogo con la posizione corrente e l'immagine.

AGGIORNAMENTO basato sui commenti scambiati con bhavik.

Domanda 2: (in base alla risposta di bhavik)
Quindi non ho bisogno di un relatore per un interatore preciso, posso avere 1 interatore e 3 relatori / viste.

  1. Nel mio caso, dovrei avere un presentatore / vista per la mappa, che è dove inizia,
  2. quindi dovrei avere un presentatore / vista per la fotocamera, nel caso in cui l'utente tocchi il pulsante della fotocamera
  3. e un relatore / vista per la vista di modifica, nel caso in cui l'utente "premi a lungo" o dopo che l'utente abbia scelto la foto dal presentatore / vista della telecamera e venga reindirizzato alla stessa vista di modifica.

È corretto?

Domanda 3: I miei metodi di confine dell'interoperatore dovrebbero sempre restituire il nulla?
Nell'esempio di bhavik stanno restituendo qualcosa, ma nel blog VIPER e nel video dello zio Bob vengono sempre restituiti nulli e il risultato arriva sotto forma di un altro metodo di limite che l'interlocutore chiama sul presentatore / controller.

Domanda 4: Il modo VIPER non utilizza un controller, ma solo un relatore per parlare all'interazione, quando il video dello zio Bob usa un controller e un presentatore per interazioni diverse con l'interactor. Quale approccio dovrei prendere?

Domanda 5: Se il mio caso d'uso è qualcosa come "Vai ad altro schermo", dovrebbe avere anche un interlocutore? Dal momento che la vista corrente dirà al relatore quale pulsante è stato premuto (quale vista andare a) e questo presentatore attuale dirà il suo wireframe "cambia in questo altro wireframe".


Sembra che i due casi d'uso sembrino avere risultati identici come "Crea un luogo con / senza immagine" con due modalità: "Con fotocamera e servizio di localizzazione" vs "Inserimento manuale dei dati".

Il caso d'uso "Con fotocamera e servizio di localizzazione" aggiunge anche una foto.

Tuttavia, mi chiedo se due modi per ottenere lo stesso risultato o un risultato identico siano considerati casi di uso singolo.

Disegnerei i due come casi d'uso separati se possibile, altrimenti ne farei uno come caso d'uso primario o predefinito e altro approccio come alternativa per ottenere lo stesso risultato finale identico.

Usa caso: Crea luogo

Flusso di base: utilizzare la fotocamera e il servizio di localizzazione

  1. L'utente preme il pulsante più.

  2. App visualizza la vista della telecamera.

  3. L'utente scatta una foto.

  4. L'app crea posto con la posizione corrente "e l'immagine".

Flusso alternativo A: utilizzare l'inserimento manuale dei dati

A.1. Utente "pressione lunga" sulla mappa.

A.2. App rilascia un pin temporaneo e visualizza la vista di modifica del luogo.

A.3. L'utente modifica le informazioni sul luogo e preme il pulsante Salva.

A. 4. L'app crea il luogo "senza immagine" e lo salva.

Ha senso ciò?

Aggiornamenti con dettagli specifici

Il View Controller responsabile della gestione di View in iOS viene infatti considerato come View in VIPER. Vedi il paragrafo "Visualizza". Un UIViewController, o una delle sue sottoclassi, implementerà il protocollo View. Quindi questo controller è la tua vista.

L'idea è che devi isolare il tuo Presenter dalla conoscenza di iOS View o del controller iOS. Dovrebbe occuparsi della vista e del controller specifici di iOS tramite semplici strutture dati come ViewModels. Se è possibile farlo, è possibile isolare con successo le dipendenze specifiche di Presenter da iOS SDK e, se lo si desidera, è possibile scrivere ed eseguire TDD o test di unità direttamente sui Presenter.

Tuttavia, più interessante una volta che si riesce ad isolare Presenter da View e ViewController, è possibile isolare facilmente Interactor da Presenter. Il presentatore dovrà passare i dati nel modulo che è accettabile per l'Interactor. Quindi Interactor non sa nulla di Presenter. È indipendente e questo semplice Interact (caso d'uso) può essere facilmente utilizzato nella GUI da riga di comando, web o desktop.

Penso che ci dovrebbe essere un caso Interactor per use. Se ci sono flussi alternativi al caso d'uso, l'interactor avrà metodi con quelle strutture dati alternative.

Nel tuo caso, CreatePlaceInteractor avrà due metodi:

CreatePlaceWithManualDataEntryResult createPlaceWithManualDataEntry(CreatePlaceWithManualDataEntryRequest)

CreatePlaceWithCameraAndLocationServiceResult createPlaceWithCameraAndLocationService(CreatePlaceWithCameraAndLocationServiceRequest)

Ci saranno tre View / Presenters:

  1. CreatePlaceChoicePresenter / View acquisirà la scelta dell'utente e invierà la richiesta al NavigationController o Wireframe, a seconda dei casi, che restituirà nuovo Presenter / Vista in base alla scelta dell'utente.

  2. CreatePlaceWithManualDataEntryPresenter / View creerà e convertirà CreatePlaceWithManualDataEntry ViewModel in CreatePlaceWithManualDataEntry Request e riceverà CreatePlaceWithManualDataEntry Result e procederà di conseguenza per visualizzare il risultato del caso d'uso sulla vista.

  3. CreatePlaceWithCameraAndLocationServicePresenter / View creerà e convertirà CreatePlaceWithCameraAndLocationService ViewModel in CreatePlaceWithCameraAndLocationService Request e riceverà il risultato createPlaceWithCameraAndLocationService e procederà di conseguenza per visualizzare il risultato del caso d'uso sulla vista.

Ci scusiamo per essere verbose su richiesta, resonse, viewmodels e nomi di metodi.


Domanda 1: Qual è il modo corretto di costruire un caso d'uso (o più di uno) con 2 modi per fare la stessa azione?

Nella progettazione VIPER, è possibile creare due metodi nello stesso Interactor adatto per ciascun primario e alternato del caso d'uso.

Domanda 2: (in base alla risposta di bhavik) Quindi non ho bisogno di un relatore per un interatore preciso, posso avere 1 interatore e 3 relatori / viste.

Sulla base della nostra discussione e dei tuoi aggiornamenti, penso di capirlo meglio.

  • Presentatore / Vista non dovrebbe interagire con più che su Interactor.
  • Presentatore / Visualizza potrebbe non interagire con nessun Interactor come nel caso di CameraView .
  • Sono viste intermedie come in Wizards.
  • Multiple Presenter / View può interagire con un singolo Interactor.
  • Interactor non si associa a nessun Presenter.
  • Single Interactor è responsabile del caso singolo e di tutti i suoi flussi alternativi. 1-1 relazione.

Quindi, dovresti avere un singolo EditPlacePresenter/View per EditPlaceInteractor che passa i dati Inserisci dati con o senza Foto.

Domanda 3: I miei metodi di confine dell'interoperatore dovrebbero sempre restituire il nulla?

Nell'esempio di bhavik stanno restituendo qualcosa, ma nel blog VIPER e nel video dello zio Bob vengono sempre restituiti nulli e il risultato arriva sotto forma di un altro metodo di limite che l'interlocutore chiama sul presentatore / controller.

Penso che tu ti stia riferendo al metodo Presenter sottostante che riceve i risultati dall'interactor.

- (void)foundUpcomingItems:(NSArray*)upcomingItems

Per il funzionamento di cui sopra, l'Interactor avrà istanze delegate che verranno cablate / applicate dal Presenter / Controller alla ricerca di risultati o dati. Ciò significa che il relatore / presentatore è legato all'interagente o il suo puntatore di riferimento o funzione di ritorno viene passato in ciascuna chiamata al metodo di interazione. È per progetto?

Penso che Interactor debba restituire i dati secondo il caso d'uso. Ad esempio, Interactor dovrebbe restituire EditPlaceResult con esito positivo o negativo.

  • In caso di successo, dovrebbe includere i dati salvati, ad esempio Place ID.
  • Se fallito, dovrebbe includere un motivo di errore.

Questo dovrebbe essere parte del caso d'uso. Altrimenti, non dovrebbe restituire nulla. Restituirà nulla e un Interactor separato verrà interrogato da Presenter per verificare se Map Place è stato aggiunto con successo o meno.

Riferimenti nel blog:

  • Il relatore contiene la logica di visualizzazione per preparare il contenuto per la visualizzazione come ricevuto dall'interactor.
  • Sta al presentatore prendere i dati restituiti dall'interactor e formattarli per la presentazione.
  • Il Presenter riceve i risultati da un Interactor e converte i risultati in un modulo che è efficiente da visualizzare in una vista.

Domanda 4: Il modo VIPER non utilizza un controller, ma solo un relatore per parlare all'interazione, quando il video dello zio Bob usa un controller e un presentatore per interazioni diverse con l'interactor. Quale approccio dovrei prendere?

È necessario definire i percorsi VIPER per la seguente navigazione:

  • Tasto fotocamera: MapView di spostarsi da MapView a CameraView (Usa posizione)
  • Pressione lunga: spostarsi da MapView a EditPlaceView (Usa coordinate)
  • Foto scattata: CameraView da CameraView a EditPlaceView
  • Salva luogo: invia a Interactor una richiesta per salvare posto con / senza foto a seconda dei casi e torna a MapView caso di esito positivo
  • Pulsante Indietro: Torna alla vista precedente basata sullo Stack di navigazione

Come per il blog VIPER, i controller di visualizzazione e di navigazione sono utilizzati da Presenter e Wireframe.

VIPER Wireframe gestisce la navigazione e fa sì che i controller di visualizzazione diventino magra, media, per visualizzare le macchine che controllano.

Fondamentalmente Wireframe astrae il controller di navigazione e fornisce invece la definizione della rotta.

Wireframe

  • Possiede UINavigationController e UIViewController
  • Responsabile della creazione di View / ViewController e dell'installazione nella finestra
  • Il routing è definito in Wireframe e contiene una logica di navigazione per descrivere quali schermate sono mostrate in quale ordine

Presentatore

  • Usa Wireframe per eseguire la navigazione
  • Per mantenere la visibilità dei controller, VIPER deve offrire ai View Controller un modo per informare le parti interessate quando un utente esegue determinate azioni: i presentatori vengono qui!
  • Il controller della vista non dovrebbe prendere decisioni basate su queste azioni, ma dovrebbe passare questi eventi insieme a qualcosa che può - I presentatori dovrebbero prendere decisioni!

Domanda 5: Se il mio caso d'uso è qualcosa come "Vai ad altro schermo", dovrebbe avere anche un interlocutore? Dal momento che la vista corrente dirà al relatore quale pulsante è stato premuto (quale vista andare a) e questo presentatore attuale dirà al suo wireframe "cambia in questo altro wireframe".

No. La navigazione come parte del caso d'uso potrebbe non richiedere Interactor. È solo la transizione. Il Presentatore di destinazione potrebbe aver bisogno di un Interactor. Ad esempio, CameraView/Presenter non ha bisogno di Interact ma EditPlaceView deve salvare il luogo.

Complessivamente: l'idea alla base dei modelli architettonici è quella di suddividere una determinata applicazione software in parti interconnesse, in modo da separare le rappresentazioni interne delle informazioni dai modi in cui le informazioni sono presentate o accettate dall'utente. MVC, MVP, MVVM, VIPER si concentrano tutti sull'isolamento di Vista, Logica e Navigazione in un modo o nell'altro.

I modelli architettonici sono limitati in ciò che intendono decomporsi. Dobbiamo capire che i modelli architettonici non decompongono o isolano tutto. Inoltre, se un modello architettonico delega alcune responsabilità a determinate parti, altre non lo fanno affatto o assegnano più responsabilità a una singola parte.

Siamo autorizzati ad estendere o limitare l'isolamento e la decomposizione nella misura in cui giustifica la causa e non impone una separazione non necessaria delle preoccupazioni che invadono il costo. È possibile scegliere di utilizzare i controller di navigazione e il relatore può fare affidamento su di essi senza le route Wireframe definite. Questi controllori saranno quindi responsabili della navigazione tra gli schermi.


Utilizza Case Modeling e considerazioni sulla progettazione di VIPER per: Creare, modificare e visualizzare "Un posto sulla mappa" per un'applicazione mobile basata su iOS

Le tue domande

  1. Cosa fare se - Creare, modificare e visualizzare azioni completate nello stesso ViewController?

  2. È una buona idea se MapViewController utilizza PlacesInteractor per recuperare le posizioni e il CurrentLocationInteractor per richiedere l'autorizzazione della posizione dell'utente e ottenere le coordinate più aggiornate?

Non è un problema combinare la logica correlata in un singolo Interactor. Ma non sarà più un "Interactor". Diventerà un "servizio" o "gestore" come in MapPlaceManager / MapPlaceService che avrà metodi come:

canCreateMapPlace
createMapPlace(Details)

getMapPlaceCount
getMapPlaceIDs
getMapPlaceDetails(ID)

canUpdateMapPlace
updateMapPlace(ID, NewDetails)

Penso che l'idea fosse quella di esporre solo le API previste per ogni caso d'uso e, quindi, Interactor - che può indicare chiaramente cosa l'utente di tale Interactor ha intenzione di fare con esso. Se ha più API che possono fare cose diverse come creare / modificare / cancellare luoghi di mappe, allora dobbiamo controllare le chiamate di metodo nel chiamante per sapere cosa sta per fare il chiamante. In questo senso, gli interlocutori sono interfacce di livello molto alto - business / requisiti. È possibile nascondere i servizi di back-end e i gestori all'interno di questi singoli Interact.

Puoi portare questa nozione il più lontano possibile e / o fattibile - fattibile piuttosto possibile. Ci sarà un estremo in cui disegneremo la linea, invece di seguirla troppo religiosamente. I sistemi aziendali tendono ad essere più formali e metodici per darti un esempio.

Nel tuo caso, quando sulla tua vista principale viene premuto un pulsante che modifica MapPlaceView in MapPlaceEditView , stai cambiando il caso d'uso che la nuova vista sta per soddisfare. Tali modifiche della vista sul posto sono considerazioni sulla progettazione della vista appropriate per i dispositivi mobili ed è anche facile da usare. Tuttavia, spesso incoraggia la complessa interfaccia grafica e la logica del presentatore disordinato. Se è gestibile, più pulito e più semplice per ViewController / Presenter per cambiare "modalità" tra "Crea, Visualizza, Modifica" - sei a posto. Non è perfetto, ma non è sbagliato. Sono front-end-partecipanti e hanno comunque il più alto livello di libertà e frequenza di cambiamenti.

Una buona progettazione dell'interfaccia utente alternativa che ho trovato utile invece di modificare i campi sul posto, è "capovolgere" le viste o qualsiasi effetto di transizione della vista. Puoi avere un MainMapPlacePresenter/ViewController e ha 3 sotto-viste - per Crea, Modifica e Visualizza. Questa vista principale è quindi responsabile della commutazione tra queste tre viste. Consente una navigazione più pulita, implementazioni del caso di utilizzo più pulito e un design pulito.

Analogamente per CurrentLocationInteractor , fa due cose: 1. richiedere l'autorizzazione per utilizzare "device location service" e 2. utilizzare "device location service". Ora, sembra che non sia affatto un Interactor. È funzionalità front-end. Ma puoi usare SaveAuthorizationInteractor per salvare la scelta dell'utente. Ma questa è una cosa diversa. Più penso, gli Interact sono responsabili di cose che riguardano il tuo sistema e non il tuo utente.

Presenter svolge tutte le attività di "user-talking" e "decision making" - possono utilizzare le API del dispositivo se necessario, ad esempio il servizio di localizzazione. È possibile creare l'interfaccia astratta ILocationService e l'implementazione wrapper denominata LocationService che assorbirà il servizio di localizzazione dei dispositivi dell'utente: implementazione di basso livello e dettagli specifici della piattaforma.

In termini di implementazione: puoi avere:

MainPresenter/MainViewController
On Load - Show MapView along with Buttons for Edit and Create Map Place

MapPresenter/MapViewController
On Load - Show Map
    Navigations - login, authorization, create, edit
    Interactions - none

MapPlaceCreatePresenter/MapPlaceCreateViewController
    On Load - call MapPlaceCreateInteractor.canCreateMapPlace - Response = {AllGood, UserNotLoggedIn, LocationIsNotAuthorized}
    Interaction - MapPlaceCreateInteractor.createMapPlace - Responses = {PlaceCreatedSuccessfully}
    Navigations - Login, Location Authorization, Back to Main View (With Response - UserLoginNeeded, UserAuthorizationForLocationAccessNeeded)

MapPlaceUpdatePresenter/MapPlaceUpdateViewController
    On Load - call MapPlaceUpdateInteractor.canUpdateMapPlace
    Interaction - MapPlaceUpdateInteractor.updateMapPlace(ExistingMapPlaceID, NewDetails) - Responses = {PlaceUpdatedSuccessfully}
    Navigations - Login, Location Authorization, Back to Main View (With Response - UserLoginNeeded, UserAuthorizationForLocationAccessNeeded)




use-case