Mobile Architecture — What design pattern I should follow?
All the developers at some point in time would have asked themselves — What is the best design pattern to build my application? Do I go with any of the design patterns like MVC, MVP, MVVM, VIPER, etc.? While all of the design patterns mentioned above have their pros and cons, any good design should follow the basic principles:
- Modularity
- Single Responsibility
- Scalability
- Testability
This blog talks about one such design that satisfies the four principles listed above and the benefits it offers to the developers with a simple example.
Modularity
Any system can be broadly divided into four logical units:
- View Controller is the core of the entire design and it has modules like dataSource, controller, and delegate. The controller is responsible for the basic setup. data source handles the business logic and the delegate handles the navigation logic.
- Network Layer has two modules namely network manager and service client that handle all the network operations.
- Presentation Layer provides the data to the screens and has modules like models, view models, and cell models. The view model is an observer in the controller which usually triggers a data reload based on the model injected into it. The view model consists of functions much à la the data source functions like
numberOfSections
,numberOfRows
,cellForRowAtIndexPath
. - UI Layer: has the views and cells that render content.
Some of the benefits modularity offers to developers include:
- Independent development: Modules can be developed independently and can be easily shared between different systems.
- Easier to test: Since the system is broken down into smaller modules. Testing each module becomes a lot easier.
Single Responsibility
All the classes have a single responsibility. For example, the cells, whose only responsibility is to display data do not need to know where the data came from. The data source which handles business logic does not have to worry about what goes on in the delegate.
Example
Let’s take a look at a real example of how all this translates into code with a simple app that fetches data from the URL and displays it in a tableView.
- The ViewController should only have a simple setUp and fetch data function. It owns the viewModel which in turn has a tableView reload function to reload the tableView every time there is a change in the ViewModel. The other components of ViewController, that are separated out and owned by the ViewController are DataSource and Delegate.
- The Service and Network Clients are simply used to fetch data.
- The service call returns a data model to be injected as a dependency to the ViewModel.
- The ViewModel has functions that are exact replicas of the DataSource. It uses the model to create data to be supplied for data source functions. The data needed by the rows/cells in the function cellForRowAtIndexpath are supplied in the form of a cell model.
PhotoCellModel
in the below example.
Delegate just handles the navigation logic and assigns the responsibility of push or present DetailViewController to the ViewController.
Benefits:
- Separation of concerns: A class is not impacted by what happens in another class.
- Better readability and error detection: Since each class is responsible for only one task, reading the code and detecting errors becomes a lot easier.
Scalability
Since the modules are decoupled from the core logic and developed independently, most of them could be passed around different screens with slight changes.
Benefits:
- Becomes much easier to extend functionality without having to change the entire system.
- Shared modules limit the amount of duplicate code written and thereby make maintenance easy.
Testing
Since we divide the system into smaller components and use dependency injection to inject objects, it becomes a lot easier to add tests.
Testing EndPoint, Service, and Data Model: Using the mock class and mock JSON, we can test the entire Service layer, Endpoint, and Data models. The System Under Test (sut)
in this case, is the Service PhotosService
.
- Testing ViewModel and Row/Cell Models
The ViewModel holds clearly separates the presentation logic from the Controller and hence testing the ViewModel thoroughly is as good as testing the Views. The System Under Test (sut)
in this case is the ViewModel PhotoViewModel
.
Benefits:
- Thanks to dependency injection, each class can be fully tested.
- Classes can be tested independently without integrating into a system.
- Makes it easier to mock classes and data.
Conclusion
The design we discussed so far can be applied to any app with slight modifications based on the requirements and complexity of the app. To summarize, a good design is one that:
- Enables the developers to work independently.
- Allows you to modify parts of your app easily.
- Scales as the system grows.
- Is easy to test.
Cheers!