S O L I D Principle with Swift

Ali Akhtar
9 min readJun 22, 2019
Thanks to Lukas for the illustration

This article is what I understand about SOLID feel free to discuss in the comment. In this blog, I cover the following topics

  1. SOLID Principle
  2. Network Manager follow the Single Responsibility and Open Close Principle
  3. Unit test Network Layer By using Complete Mocking
  4. Why Complete Mocking should follow the Dependency Inversion Principle
  5. How Interface Segregation solves Liskov Substitution Principle problem
  6. Created A persistent Layer independent of Any persistent framework By following Dependency Inversion Principle

S O L I D

In object-oriented computer programming, SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable.

S → Single Responsibility Principle (SRP)

The single responsibility principle is a computer programming principle that states that every module, class, or function[1] should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. [source]

Class Single Responsibility Principle

A class should have one and only one reason to change OR A class should have only one responsibility

We have a class NetworkingManager which you can also infer responsible for networking related stuff. This class responsibility includes:

  1. Get data from server using UrlSession (a network library),
  2. Expect Response in JSON,
  3. Parse JSON into the model.

So when this class needs to change/update? Main reasons to change this class include:

  1. Now Requirement is to use Alamofire (new network library )instead of UrlSession.
  2. The server is sending XML response instead of JSON.

Since the class has mainly two reasons to change we are breaking SRP

Figure 1

As shown in Figure 2 we removed parsing logic from this class to achieve the SRP principle to some extent. Now instead of parse response, we are sending swift raw Data type to the caller:

Figure 2

Another Example

As shown in Figure 3 we have a class MovieListService is responsible for movie specific network-related service. This class responsibility includes:

  1. Expect Swift Data type format response.
  2. Request Creation specific to Movie List (Movie List is an API that fetches the latest upcoming movie).
  3. Parse JSON data since we programming to an interface TranslationLayer can parse XML we just need to change implementation at run time.

Find a number of main reasons to change this class:

  1. Request Creation logic can change.

You are thinking that server can send data in XML and the answer is since we are programming to an interface we can create XMLTranslation and inject it and the method remain the same so to conclude it this class is following SRP

Figure 3

O → Open–closed principle

Modules/Classes/entities should be open for extension but closed for modification. (to some extent)

As shown in Figure 4 PassportRepository have CRUD operation code specific to Passport Model. This class can easily modify if we moved to CoreData(or any other persistence framework) because it is tightly coupled with the Realm. The Realm specific dependency includes:

  1. Calling save method directly with realmManager instance.
  2. Map to PassportDTO to realm Specific Model.

This class is not following the open-close principle:

Figure 4

As shown in Figure 5 we make PassportRepository class closed for modification by doing the following steps:

  1. Created a DataManager protocol that has a basic interface for the CRUD operation task and can conform by any persistent framework like CoreData / Realm.
  2. Created a class RealmDataManager. It has a reference of realm database instance which will be injected by the client. For Core Data the class should be CoreDataManager that will have a reference to Core Data stack.
  3. On PassportRepository we query to data layer without dependent on any persistence framework since we program to an interface if we injected CoreDataManger it will call its CRUD operation and if we inject MockDataManager for testing it will call its CRUD operation. This class has no effect if we change the persistence framework so we closed this class for modification.
  4. You can extend this class in two ways By adding other CRUD operation methods Or by changing new persistence framework See this blog where I showed How to Architect persistence framework.
Figure 5

Benefits Of Open Closed Principle

  1. Minimizing risk of breaking the existing code base,
  2. Flexibility,
  3. Maintenance,
  4. Loose Coupling.

Another Example

This example has taken exactly from the Book “Head First Design Pattern” written By Eric Freeman

Let’s say there is a Starbuzz Coffee shop who is selling different types of Coffee and they have a system that calculates cost of the Coffee. When they first went into business they designed their classes like this…

As shown in Figure 6 every coffee is conforming to Beverage protocol and implemented their own cost method:

Figure 6

Now Requirement Change In addition to your coffee, you can also ask for several condiments like steamed milk, soy, and mocha (otherwise known as chocolate), and have it all topped off with whipped milk. Starbuzz charges a bit for each of these, so they really need to get them built into their order system.

As shown in Figure 7. It’s pretty obvious that Starbuzz has created a maintenance nightmare for themselves. What happens when the price of Mocha goes up? They had to change in All coffee who has a topping Mocha (class explosion)

Figure 7

Now Let’s try to use Protocol Extension. As shown in Figure 8 calcualteTopping in protocol extension will calculate the costs for all of the condiments, while the cost() in the conforming classes will extend that functionality to include costs for that specific beverage type.

Each cost() method needs to compute the cost of the beverage and then add in the condiments by calling the default implementation of Beverage protocol:

Figure 8

Think Is Protocol extension that we created so far is closed for modification? What requirements or other factors might change that will impact this design?

  1. Price changes for condiments will enforce to alter the existing code.
  2. New condiments will force us to add new methods and alter the calcualteTopping method in the protocol extension.
  3. We may have new beverages. For some of these beverages (iced tea?), the condiments may not be appropriate.
  4. What if the client wants double Mocha topping.

In short We are breaking “Classes should be open for extension, but closed for modification” principle

Decorator Pattern to the Rescue

The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

As shown in Figure 9 every coffee is conforming to Beverage protocol and implemented cost method:

Figure 9

As shown in Figure 10 we created CondimentDecorator Protocol Inherited from Beverage to calculate the cost of our beverage with condiments in this case Mocha. First, we delegate the call to the object we’re decorating, so that it can compute the cost; then, we add the cost of Mocha to the result we are adding responsibilities to object dynamically by adding additional responsibilities as shown below and The Flow will look like:

  1. Fill Coffee with Espresso,
  2. Add Mocha Topping to it dynamically,
  3. Add Soy Topping to it dynamically by giving object we created new responsibilities,
  4. Calculate Cost by using delegation.
Figure 10

Is it following Open — Close Principle let’s say if we want to add new topping we can easily add it by creating Class and conform to CondimentDecorator protocol we are not modifying existing code What happened if price of Mocha increases in this case we need to modify existing code but which we can ignore since it is following SRP As I said earlier we can follow this principle to some extent.

L → Liskov substitution principle (LSP)

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

As shown in Figure 11 we created dataManagers object with the reference of their base class and subclasses preserving the correctness of that program. Means it is reading and writing data in a program properly.

Point 1 → Subclasses don’t have any unrelated feature of the base classes that it don’t require

Poin 2 → Subclasses don’t have any special if condition when it is using by the reference of the base class

If point 1 and Poin 2 is valid then likely it is obeying Liskov substitution principle. In this case it is 100 % follow the rules

LSP →Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”

Figure 11

As shown in Figure we need to support the in-memory database as well which will not save into the disk. What we did:

  1. Created a class InMemoryRealmManager and conform to DataManager and implemented it’s read logic since we don’t need to save method we throw fatalError.
  2. Added InMemoryRealmManager to array of dataManagers.
  3. Forget to see an object with the reference of their base class and subclasses not preserving the correctness of the program.
  4. Run the application and crash,
Figure 12

As shown in Figure 13 we finally fixed the crash now Let’s check if we still follow LSP:

  1. We are breaking Point 1 because InMemoryRealmManager has unrelated feature write().
  2. Subclasses have special if condition when it is using by the reference of the base class.
Figure 13

So the Question is How to solve it and the solution of this answer is the next SOLID principle as shown in Figure 15.

I → Interface segregation principle (ISP)

Many client-specific interfaces are better than one general-purpose interface

It is saying that Clients should not be forced to depend upon interfaces that they do not use. In previous example our class InMemoryRealmManager conforming to an interface where some method is useless for this class.

As shown in Figure 14 we created a general-purpose interface DataManager and many classes conforming to it. There are some features that are not needed by some of the classes . So we need to break this into client-specific interfaces so that it should:

  1. Increased reusability,
  2. Follow LSP,
  3. Compose features that only required instead of getting unrelated things,
  4. No special logic to cater unrelated feature:
Figure 14

Following it’s the code supporting the Interface Segregation Principle. By splitting the DataManger interface in 2 different interfaces(Readable, Writable) the new InMemoryRealmManager class is no longer forced to implement the write method. Also, if we need another functionality for the robot-like fetching we create another interface Fetchable with a method fetch.

Figure 15

Benefits of Interface segregation principle

  1. Provide thin interfaces that can be reused in multiple places,
  2. Provide groups of operations that logically belong together.
  3. less likely to break the Liskov Substitution Principle,
  4. Flexibility.

D → Dependency inversion principle (DIP)

One should “depend upon abstractions, [not] concretions. OR (Program to an interface ) OR (Abstractions should never depend upon details. Details should depend upon abstractions.)

Example 1

In this blog I created a persistence layer that is independent on any persistence framework using Dependency inversion principle. It has a Repository class that is talking to persistence framework without even know which framework is talking to since it depends upon their abstraction instead of their concrete implementation

Example 2

In this blog I talked about how to test your network layer using three strategies that apple recommended. One of the strategy you will see in the section “Complete Mocking” where I used Dependency inversion principle to test it and One thing to note Complete Mocking is not possible if we are not following Dependency inversion principle.

Benefits of Dependency inversion principle

  1. Testability,
  2. Loose Coupling,
  3. Flexibility,
  4. Scalability.

Useful Links

--

--

Ali Akhtar

Senior iOS Engineer | HungerStation | Delivery Hero