Goal of this exercise is to improve my code design. I am hearing a lot about SOLID design principles and “uncle Bob”, so I decided to understand its ideas. This exercise is my attempt to implement this ideas on practice in simple and modern Swift application. Although SOLID principles explained with C++ examples, its good Object Oriented Design principles can be applied to Swift.
As mentioned by Robert C. Martin before, this principles is only recommendations and is not a law. Sometimes it is better to violate this good design principles but only when necessary. I also have to mentions that this is only mine interpretation, I can be wrong. Please write to me if you have any feedback.
Applications accesses Flickr API and displays image gallery, displays meta data for each image.
To satisfy acceptance criteria we need to display collection of photos, be able to show all metadata for each photo and perform actions on each photo. I designed 3 screens and to implement them we will need following:
Let’s assume, later we will need to add sorting feature for the next version of application.
For getting photos from Flickr singe service class is created. This class sends data to
For the UI we use single Storyboard file.
Good Model Design.
What makes design good or bad? How we can judge it? There’s 3 warnings signs in which we can evaluate design quality. This was described in The Dependency Inversion Principle.
- Code that hard to change.
- Every change breaks code in unexpected place.
- Code can’t be reused.
In order to create reusable design we need to think what parts of application can be changed in the future and what kind of change we should be ready for. We can improve naive design in many ways. Let’s focus on model design and UI design separately and start with model.
Lets say everything user can’t see is part of the model. First decision that I made is to write separate
NetworkService protocol. Swift protocol itself can have only method definition, not implementation. I extended
NetworkService and chose to use Apple
URLSession to request data with status and optional error object.
Let’s imagine this source code need to be used in another project which relies on third party Alamofire library. In this case, developer need to replace
NetworkService extension. If developer keeps same method definition, good news,
PhotosCollectionViewController will not need to change. Job well done.
Another struct is required to specify request path, I called it
FlickrService. It has endpoint path and data type parameter. I also created
FlickrDataModel struct. In this struct I added method to parse data into Flickr model taking advantage of Swift 4 Standard Library with
JSONDecoder. I chose to use nested Decodable Structs to mirror data model from an Flickr endpoint.
Unlike naive model, in this design I separated Flickr related logic and networking logic into separate abstractions. This enables easy code reuse, and also allows easier refactoring of one part of application without breaking other part.
PhotosCollectionViewController communicates with lower level networking module through higher level
FlickrService class which conforms to abstract
NetworkService protocol and can perform request. I believe this is good example of The Dependency Inversion Principle. Higher level abstractions doesn’t depend on lower level implementation.
The rest of application.
So far I spend most of my attention into
PhotosCollectionViewController. There’s 2 other goals:
- View controller for displaying photo in full screen and performing some actions: sharing photo, opening original Flickr web page.
- Displaying metadata in full screen.
This can be done with 2 or 1 view controller. Whenever
Photo metadata changes, this view controllers and UI would need to change as well. I chose to split metadata viewer and full screen photo view into 2 different view controllers. I created 2 Storyboard files and took advantage of Storyboard References. That’s better than using single Storyboard file, now engineers can work simultaneously. Storyboard itself allows us to separate view logic from business logic. This allows changes in UI design and layout without need to change view controller code.
I assume one can say that this design violates The Dependency Inversion Principle — changes in
Photo object model, replacement it with another object, or adding new data source types, require changes in UI and controllers. For this simple exercise I chose to keep UI static. We can’t be ready to every possible change and need to think carefully what areas of application can change. Because this is photo browsing app I keep core UI functionality together. I believe it is important to understand purpose of the application, in our case it is displaying photos with meta data. We should design our code to be ready for additional features and changes in implementation. In case when core purpose changes, it is better to start from scratch.
Separating User Interface.
Well done, version one finished. I am satisfied with design and core requirements. What if we would like to implement sort function? It can be done inside the same collection view controller. But this would be bad design and eventually lead to well known Massive View Controller problem. What if we want to reuse sorting module or change its implementation? The Dependency Inversion Principle states that UI should not be connected with business logic directly, UI should communicate through higher level abstractions. Ideally, sorting feature should have its own MVC.
To fix this, we can create
SortOptions struct which defines
SortType enum and its order. It also contains text to be displayed in UI. We use
UISegmentedControl, I consider it as low lever class which should know nothing about business logic. We extend this control with
SortControlextension which would take
SortOptions and would configure
UISegmentedControl. to display appropriate number of segments with appropriate labels.
SortOptions struct into
PhotosCollectionViewController which is fine but I didn’t connect
UISegmentedControl directly to
PhotosCollectionViewController like it’s done in naive model. Instead, I created
SortViewController and took advantage of Cocoa Touch Container View. This allows me to move
UISegmentedControl into separate Storyboard file, meaning if UI changes,
PhotosCollectionViewController will work the same, only
SortViewController would require change. As long as our collection view conforms to
SortableCollection protocol and communicates through
SortViewController, design does not violates The Dependency Inversion Principle.
Project is available on GitHub.