Demystifying SOLID principles in Swift with a Deeplink Manager
SOLID principles have huge impact on building testable, maintainable and readable code and systems. Each of its letters stands for a principle and applying them all in a system or module brings advantages.
In this article, I’m going to demonstrate how we built a Deeplink Manager module based on the principles one-by-one.
Single Responsibility Principle (SRP)
This principle points that each module/component/class must have a single responsibility. For our module, each deeplink should have its own `single` responsibility for parsing a URL to the expected type. To achieve so, let’s create a swift struct
for a link:
At the same time, each deeplink should have a responsible router for routing purposes. We will come to it later.
Open/Closed Principle (OCP)
Modules must be open for extensions, but closed to modifications
Meaning the system must depend on features which can be extended without need to modify it. To be more specific, when trying to add a new deeplink type to existing system, other deeplinks and their implementation should not be affected.
And how to achieve that? Links need to conform to a type which is a protocol in Swift:
Now let’s create a manager class to allow point of entry for our deeplinks:
Using Applinkables as supported link types, allows us to conform to open/closed principle where we can add new link types without breaking existing links.
We can create CustomerLink:
And, all we need to change, is modifying link
variable on DeeplinkManager
and add CustomerLink()
instance to it.
Liskov Substitution Principle (LSP)
Supporting open/closed principle, LSP states that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.
To do so, we need move getLinkable
function to AppLinkable
. This will allow us to iterate links without knowing which link it is.
Now adding function getLinkable
on DeeplinkManager allowing us to iterate through available links and replace them with the value returned by AppLinkable`s getLinkable
function to receive the link with parameters if needed.
Interface Segregation Principle (ISP)
The ISP states that no client should be forced to depend on methods it does not use.
So far, we know that all links have to implement getLinkable
function provided by AppLinkable
. Diving deep into our DeepLinkManager, we receive a requirement stating that some notifications can with opened from a push notification payload which is [AnyHashable: Any]
type, tough all deeplinks will support opening from a url known as universal link on iOS.
To achieve it, we need to add a function in AppLinkable to check whether link is satisfied by a payload.
Now, we need to implement new getLinkable
with payload function in our all links, whether it’s supporting payload or not. This is violating ICP, right?
Thanks to swift extension, we can actually make it optional for the links those do not need to implement this function. So by default, we can extend AppLinkable’s to return nil
for payload function:
Dependency Inversion Principle (DIP)
High-level components should not depend on low-level components or modules and low-level components should depend on abstraction, not high-level component.
Let’s finalize our DeeplinkManager by implementing its routing functions by following DIP.
We need our application to listen to deeplinks when they need to be routed. In our application we need to know which link should be navigated without knowing its type. At the same time, we need to know what the link is to route to the related link detail.Let’s create AppLinkRoutable (following OCP):
And let’s create AppLinkRoutable
for CustomerLink
Now we have a routable for CustomerLink, we need to implement route function. Following LSP, let’s add a route function to AppLinkable
, assuming each link must have a routing logic. Final AppLinkable will be:
Now we need to implement route function for CustomerLink
.
As we have finalized CustomerLink with its routing, now, it’s time to conform our AppDelegate
toCustomerLinkRoutable
.
Finally, let’s implement DeepLinkManager.getLinkable(payload:)
:
Conclusion
We created a DeepLinkManager class which manages deeplink presentation and routing. We can add as much as deeplinks we need, without breaking our application (in this case AppDelegate). To keep it simple, I implemented deeplink routing on AppDelegate
. AppDelegate is agnostic about the link it received, though it has routing implementation for each link through AppLinkRoutable
conformance. Each link is responsible for calling conforming listener (in this case AppDelegate) through its routable.
I hope the article could help you to clarify SOLID principles and their usages on real life app development.
Please feel free to share your impressions on comments.