Uso del modello Repository in Hilo (app di Windows Store in C++ e XAML)

Applies to Windows only

Da: Sviluppo di un'app di Windows Store end-to-end con C++ e XAML: Hilo

Logo Patterns & Practices

Pagina precedente | Pagina successiva

Per Hilo C++ abbiamo deciso di usare il modello Repository per separare la logica per il recupero dei dati dal file system di Windows 8 dalla logica di presentazione che agisce su tali dati. Abbiamo scelto il modello Repository perché volevamo che l'app fosse più gestibile e che consentisse di eseguire facilmente unit test.

Download

Download dell'esempio Hilo
Download guida (PDF)

Dopo aver scaricato il codice, per le istruzioni vedi Introduzione a Hilo.

In questo articolo verranno illustrati gli argomenti seguenti

  • Come implementare il modello Repository per un'app di Windows Store in C++.
  • Come usare il modello Repository per eseguire unit test dell'app.

Si applica a

  • Windows Runtime per Windows 8
  • Estensioni del componente di Visual C++ (C++/CX)
  • XAML

Introduzione

Il modello Repository separa la logica che recupera e salva in modo permanente i dati e li mappa al modello sottostante dalla logica di business che agisce su tali dati. Il repository è responsabile delle operazioni seguenti:

  • Mediazione tra l'origine dati e i livelli business dell'app.
  • Interrogazione dei dati presenti nell'origine dati.
  • Mapping dei dati dall'origine dati al modello.
  • Salvataggio permanente delle modifiche del modello nell'origine dati.

La separazione della logica di business dall'origine dati offre i vantaggi seguenti:

  • Accesso centralizzato all'origine dati sottostante tramite un livello di accesso ai dati.
  • Isolamento del livello di accesso ai dati per il supporto di unit test.
  • Miglioramento della gestibilità del codice grazie alla separazione della logica di business dalla logica di accesso ai dati.

Per altre informazioni, vedi Modello Repository.

Hilo implementa il modello Repository tramite la definizione di una classe di base astratta che dispone di funzioni membro virtuali pure. Tali funzioni forniscono una classe di base da cui le altre classi repository devono ereditare. Una funzione membro virtuale pura è una funzione membro definita come virtuale e assegnata a 0. Una classe di base astratta di questo tipo non può essere usata per creare istanze di oggetti e funge solo da interfaccia. Di conseguenza, se è necessario creare istanze della sottoclasse di questa classe astratta, è necessario implementare ognuna delle funzioni virtuali, determinando così il supporto dell'interfaccia definita dalla classe di base astratta da parte della sottoclasse.

La classe di base astratta Repository definisce un numero di funzioni membro virtuale pure che devono essere sovrascritte dalle funzioni membro che hanno le stesse firme in una classe derivata. La classe FileSystemRepository deriva dalla classe Repository e sovrascrive le funzioni membro virtuali pure per interrogare il file system per le foto. Un'istanza condivisa della classe FileSystemRepository viene quindi usata dalla classe TileUpdateScheduler e dalle classi del modello visualizzazione per leggere le foto dal file system. L'illustrazione seguente mostra questa relazione.

Diagramma a blocchi per Repository

La classe StubRepository implementa inoltre le funzioni membro virtuali pure definite nella classe di base astratta Repository al fine di fornire un'implementazione del repository fittizia per l'esecuzione di unit test. Un'istanza della classe StubRepository può quindi essere passata nelle classi del modello visualizzazione dagli unit test. Anziché restituire le foto dal file system, questa classe fittizia restituisce semplicemente oggetti foto fittizi. Per altre informazioni sugli unit test, vedi Test dell'app.

L'illustrazione seguente mostra una panoramica dell'architettura del repository. Il vantaggio principale di questa architettura è costituito dal fatto che un repository che accede a un'origine dati, ad esempio il file system, può essere facilmente sostituito con un repository che accede a un'origine dati diversa, ad esempio il cloud.

Classi stub per unit test

Per implementare un nuovo repository che deriva dalla classe di base astratta Repository, devi implementare anche le interfacce IPhoto, IPhotoGroup, IYearGroup e IMonthBlock. In Hilo queste interfacce sono implementate rispettivamente dalle classi Photo, HubPhotoGroup, MonthGroup, YearGroup e MonthBlock.

Per spiegare il modo in cui Hilo implementa e usa la classe Repository, abbiamo fornito una simulazione di codice che mostra il modo in cui la pagina Ruota recupera una foto dal file system usando la classe Repository.

[Torna all'inizio]

Simulazione di codice

In Hilo la classe App contiene una variabile membro, m_repository, di tipo Repository, in cui sono create istanze come un puntatore condiviso del tipo FileSystemRepository nel costruttore della classe. Questa istanza è creata come puntatore condiviso, pertanto nell'applicazione è presente una sola istanza della classe FileSystemRepository che viene passata tra le classi necessarie.

App.xaml.cpp


m_exceptionPolicy = ExceptionPolicyFactory::GetCurrentPolicy();
m_repository = std::make_shared<FileSystemRepository>(m_exceptionPolicy);


Il metodo OnLaunched della classe App passa l'istanza del puntatore condiviso dell'elemento FileSystemRepository al metodo ScheduleUpdateAsync della classe TileUpdateScheduler. Il metodo ScheduleUpdateAsync usa questa istanza del puntatore condiviso per recuperare le foto da cui vengono generate le anteprime per il riquadro animato dell'app.

La classe App espone un'istanza singleton della classe FileSystemRepository tramite un metodo GetRepository richiamato dal costruttore della classe ViewModelLocator per recuperare e archiviare l'istanza del puntatore condiviso della classe FileSystemRepository.

ViewModelLocator.cpp


m_repository = safe_cast<Hilo::App^>(Windows::UI::Xaml::Application::Current)->GetRepository();


Poiché entrambe le classi ViewModelLocator e TileUpdateScheduler prevedono repository di tipo shared_ptr<Repository>, è possibile usare qualsiasi altra implementazione del repository che implementa la classe di base astratta Repository.

La classe ViewModelLocator dispone di proprietà che recuperano un oggetto modello visualizzazione per ogni pagina dell'app. Per altre informazioni, vedi Uso del modello di progettazione MVVM. Nell'esempio di codice seguente viene mostrata la proprietà RotateImageVM della classe ViewModelLocator.

ViewModelLocator.cpp


RotateImageViewModel^ ViewModelLocator::RotateImageVM::get()
{
    return ref new RotateImageViewModel(m_repository, m_exceptionPolicy);
}


La proprietà RotateImageVM crea una nuova istanza della classe RotateImageViewModel e passa l'istanza del puntatore condiviso della classe FileSystemRepository al costruttore RotateImageViewModel. L'istanza del puntatore condiviso della classe FileSystemRepository viene quindi archiviata nella variabile membro m_repository della classe RotateImageViewModel, come mostrato nell'esempio di codice seguente.

RotateImageViewModel.cpp


RotateImageViewModel::RotateImageViewModel(shared_ptr<Repository> repository, shared_ptr<ExceptionPolicy> exceptionPolicy) : 
    ImageBase(exceptionPolicy), m_repository(repository), m_imageMargin(Thickness(0.0)), m_getPhotoAsyncIsRunning(false),
    m_inProgress(false), m_isSaving(false), m_rotationAngle(0.0)


Il controllo Image nella classe RotateImageView è associato alla proprietà Photo della classe RotateImageViewModel. A sua volta, la proprietà Photo richiama il metodo GetImagePhotoAsync per recuperare la foto per la visualizzazione, come mostrato nell'esempio di codice seguente.

RotateImageViewModel.cpp


concurrency::task<IPhotoImage^> RotateImageViewModel::GetImagePhotoAsync()
{
    assert(IsMainThread());
    return m_repository->GetSinglePhotoAsync(m_photoPath);
}


GetImagePhotoAsync richiama GetSinglePhotoAsync sull'istanza del puntatore condiviso della classe FileSystemRepository archiviata nella variabile membro m_repository.

[Torna all'inizio]

Interrogazione del file system

Hilo usa la classe FileSystemRepository per cercare le foto nell araccolta di immagini. I modelli di visualizzazione usano questa classe per fornire le foto da visualizzare.

Ad esempio, la classe RotateImageViewModel usa il metodo GetImagePhotoAsync per restituire una foto da visualizzare nella pagina di rotazione. Questo metodo, a sua volta, richiama il metodo GetSinglePhotoAsync nell'elemento FileSystemRepository. Ecco il codice.

FileSystemRepository.cpp


task<IPhotoImage^> FileSystemRepository::GetSinglePhotoAsync(String^ photoPath)
{
    assert(IsMainThread());
    String^ query = "System.ParsingPath:=\"" + photoPath + "\"";    
    auto fileQuery = CreateFileQuery(KnownFolders::PicturesLibrary, query, IndexerOption::DoNotUseIndexer);
    shared_ptr<ExceptionPolicy> policy = m_exceptionPolicy;
    return create_task(fileQuery->GetFilesAsync(0, 1)).then([policy](IVectorView<StorageFile^>^ files) -> IPhotoImage^
    {
        if (files->Size > 0)
        {
            IPhotoImage^ photo = (ref new Photo(files->GetAt(0), ref new NullPhotoGroup(), policy))->GetPhotoImage();
            create_task(photo->InitializeAsync());
            return photo;
        }
        else
        {
            return nullptr;
        }
    }, task_continuation_context::use_current());
}


Il metodo GetSinglePhotoAsync chiama CreateFileQuery per creare una query di file che verrà usata per recuperare informazioni sul file system tramite la sintassi di ricerca avanzata. Viene quindi effettuata la chiamata GetFilesAsync per eseguire la query e l'operazione asincrona risultante restituisce una raccolta IVectorView dei riferimenti StorageFile. Ogni oggetto StorageFile rappresenta un file del file system corrispondente alla query. In questo caso, tuttavia, l'insieme IVectorView conterrà al massimo un oggetto StorageFile, poiché GetFilesAsync restituisce solo un file corrispondente alla query. La continuazione basata su valori, che viene eseguita nel thread principale, crea e inizializza quindi un'istanza della classe PhotoImage per rappresentare il singolo file restituito da GetFilesAsync. L'istanza di PhotoImage viene quindi restituita da GetSinglePhotoAsync a GetImagePhotoAsync nella classe RotateImageViewModel.

[Torna all'inizio]

Rilevamento delle modifiche del file system

Alcune pagine dell'app rispondono alle modifiche del file system nella raccolta di immagini mentre l'app è in esecuzione. La pagina dell'hub, la pagina del visualizzatore immagini e la pagina della visualizzazione dell'immagine si aggiornano dopo aver rilevato una modifica del file system sottostante alle immagini. La pagina dell'hub e la pagina del visualizzatore immagini si aggiornano non più di una volta ogni 30 secondi anche se le notifiche di modifica dei file sono più frequenti di questo intervallo di tempo. Questo per evitare che le pagine vengano aggiornate troppo spesso in risposta all'aggiunta di immagini alla raccolta mentre l'app è in esecuzione.

Per implementare questa soluzione, le classi HubPhotoGroup, ImageBrowserViewModel e ImageViewModel creano nei relativi costruttori un oggetto function che verrà richiamato quando le foto vengono aggiunte, eliminate o modificate nelle cartelle sulle quali è stata eseguita la query. L'oggetto function viene quindi passato nel metodo AddObserver della classe FileSystemRepository. Ecco il codice.

ImageViewModel.cpp


auto wr = WeakReference(this);
function<void()> callback = [wr] {
    auto vm = wr.Resolve<ImageViewModel>();
    if (nullptr != vm)
    {
        vm->OnDataChanged();
    }
};
m_repository->AddObserver(callback, PageType::Image);


Questo codice consente di chiamare il metodo OnDataChanged nell'elemento ImageViewModel se il file system sottostante viene modificato mentre l'app è in esecuzione. Quando viene richiamato il metodo OnDataChanged, il file system viene interrogato di nuovo per aggiornare le anteprime delle foto nella sequenza. Il metodo AddObserver nella classe FileSystemRepository archivia semplicemente l'oggetto function in una variabile membro per eventuali usi futuri.

Quando il metodo QueryPhotosAsync nella classe ImageViewModel viene richiamato per recuperare le foto da visualizzare nella sequenza, il metodo GetPhotosForDateRangeQueryAsync verrà richiamato nella classe FileSystemRepository. Questo metodo interroga la raccolta di immagini per trovare le foto incluse in un intervallo di date specifico e restituisce le eventuali foto corrispondenti. Dopo la creazione della query, verrà creato un oggetto QueryChange per registrare un oggetto function da richiamare se i risultati della query sul file system cambiano. Ecco il codice.

FileSystemRepository.cpp


m_allPhotosQueryChange = (m_imageViewModelCallback != nullptr) ? ref new QueryChange(fileQuery, m_imageViewModelCallback) : nullptr;


La classe QueryChange viene usata per monitorare la query per le modifiche del file system. Il costruttore accetta una query di file e un oggetto function. Registra quindi un gestore per l'evento ContentsChanged che viene generato quando viene aggiunto, eliminato o modificato un elemento nella cartella interrogata. Quando questo evento viene generato, l'oggetto function passato nella classe viene eseguito.

L'effetto complessivo di questa operazione è che la pagina della visualizzazione dell'immagine visualizza nella sequenza una raccolta di anteprime di foto incluse in un intervallo di date specifico e la foto intera per l'anteprima selezionata. Se le foto incluse in quell'intervallo di date vengono aggiunte, eliminate o modificate nel file system, l'app riceve una notifica che indica che il contenuto del file system pertinente è stato modificato e che le anteprime verranno aggiornate.

Nota  Il gestore per questo evento rimane registrato quando l'app è in modalità di sospensione, anche se in questa modalità l'app non riceverà eventi ContentsChanged. Quando l'app viene ripresa, la visualizzazione corrente viene aggiornata.

Quando l'app viene ripresa dallo stato di sospensione, il metodo OnResume nella classe App richiama il metodo NotifyAllObservers nella classe FileSystemRepository, che, a sua volta, richiama gli oggetti function per le pagine hub, visualizzatore immagini e visualizzazione dell'immagine per aggiornare il contenuto pertinente.

[Torna all'inizio]

 

 

Mostra:
© 2014 Microsoft