Monday, 24 September 2012

The Repository Pattern in C++/CX

In a previous blog post I demonstrated how to implement the MVVM pattern in C++/CX, by way of a small sample app that allows the user to view the photos in their Pictures library. In this blog post I will extend the sample app so that access to the underlying data source, in this case the file system, is through a centralized data access layer. The main advantage of this approach is that a repository that accesses a data source, such as the file system, can easily be swapped out for a repository that access a different data source, such as the cloud.

The sample application can be downloaded here.

The Repository Pattern

The repository pattern separates the logic that retrieves and persists data from the business logic that acts upon that data. The repository is responsible for:

  • Mediating between the data source and the business layers of the app.
  • Querying the data source for data.
  • Persisting changes to the data source.

This separation of business logic from the data source has benefits, including:

  • Centralizing access to the underlying data source via a data access layer.
  • Isolating the data access layer to support unit testing.
  • Improving the code’s maintainability by separating business logic from data access logic.

For more information, see The Repository Pattern.

Implementing the Repository Pattern in C++/CX

The sample app implements the repository pattern by defining an abstract base class with pure virtual member functions, to provide a base class from which other repository classes must inherit. A pure virtual member function is a member function that is defined as virtual and is assigned to 0. Such an abstract base class can’t be used to instantiate objects and serves only to act as an interface. Therefore, if a subclass of this abstract class needs to be instantiated, it has to implement each of the virtual functions, which results in it supporting the interface defined by the abstract base class.

The Repository abstract base class defines a number of pure virtual member functions, which must be overridden by member functions with the same signatures in a derived class. The FileSystemRepository class derives from the Repository class, and overrides the pure virtual member functions to query the file system for photos. A shared instance of the FileSystemRepository class is then used by the view model classes to read photos from the file system. The following illustration shows this relationship:

repository

Using this architecture also allows mock implementations of the repository to be created, to support unit testing. A mock implementation could be passed into the view model classes from unit tests, with the mock class simply returning mock photos rather than actual photos from the file system. For more information, see the Hilo project.

In the sample app, the ViewModelLocator class contains a member variable, m_repository, of type Repository, which is instantiated as a shared pointer of type FileSystemRepository in the class constructor. This instance is created as a shared pointer so that there’s only a single instance of the FileSystemRepository class in the app, which is then passed between the required classes.

ViewModelLocator::ViewModelLocator()
{
    m_repository = std::make_shared<FileSystemRepository>();
}
The ViewModelLocator class has properties that retrieve a view model object for each page of the app.
PhotoViewModel^ ViewModelLocator::PhotoVM::get()
{
    return ref new PhotoViewModel(m_repository);
}
The PhotoVM property creates a new instance of the PhotoViewModel class, and passes in the shared pointer instance of the FileSystemRepository class, to the PhotoViewModel constructor. The shared pointer instance of the FileSystemRepository class is then stored in the m_repository member variable of the PhotoViewModel class.
PhotoViewModel::PhotoViewModel(shared_ptr<Repository> repository) : 
    m_repository(repository)
{
}
The Image control in the PhotoView class binds to the Photo property of the PhotoViewModel class. In turn, the Photo property invokes the GetPhotoAsync member function to retrieve the photo for display.
task<FileInformation^> PhotoViewModel::GetPhotoAsync()
{
    return m_repository->GetPhotoAsync(m_path);
}
GetPhotoAsync invokes GetPhotoAsync on the shared pointer instance of the FileSystemRepository class that is stored in the m_repository member variable. The Repository abstract base class defines two pure virtual member functions, including GetPhotoAsync. The FileSystemRepository class inherits from the Repository class, and provides overridden implementations of the member functions. Here’s the code for the GetPhotoAsync member function.
task<FileInformation^> FileSystemRepository::GetPhotoAsync(String^ path)
{
    auto fileQuery = CreateFileQuery(KnownFolders::PicturesLibrary, path, IndexerOption::UseIndexerWhenAvailable);
    auto fif = ref new FileInformationFactory(fileQuery, ThumbnailMode::PicturesView);
 
    return create_task(fif->GetFilesAsync(0, 1)).then([](IVectorView<FileInformation^>^ files)
    {
        FileInformation^ file = nullptr;
        auto size = files->Size;
 
        if (size > 0)
        {
            file = files->GetAt(0);
        }
        return file;
    }, task_continuation_context::use_current());
}
The GetPhotoAsync member function calls an inline function, CreateFileQuery, to create a file query that will be used by an instance of the FileInformationFactory class to query the file system using Advanced Query Syntax. GetFilesAsync is then called on the FileInformationFactory instance, which returns an IVectorView collection of FileInformation objects, representing each file found in the file system that matches the query. However, in this case the IVectorView collection will only contain a maximum of one FileInformation object, as GetFilesAsync will only return one file that matches the query. The value-based continuation, which runs on the main thread, then gets the first file from the IVectorView collection, before returning it to the GetPhotoAsync member function in the PhotoViewModel class, which in turn returns it to the Photo property in that class, for display.

Summary

The repository pattern can be implemented in C++/CX by defining an abstract base class with pure virtual member functions. Subclasses of this class can then implement each of the virtual functions, which results in the subclass supporting the interface defined by the abstract base class. The main advantage of this approach is that a repository that accesses a data source, such as the file system, can easily be swapped out for a repository that access a different data source, such as the cloud.

No comments:

Post a Comment