SOLID: Liskov Substitution Principle(LSP)

Mohshin Shah
3 min readFeb 8, 2021

LSV is actually an extension to OCP.
Meaning, it helps to correct your OCP implementation correct way.

If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program

Inheritance:
Objects of a super class should be replaceable with the object of Sub Classes

Let understand this with a violation

class DeliveryProvider {
func deliver(to address: String) {
print("Package delviered to: \(address)")
}
}
class ExpressDeliveryProvider {
override func deliver(to address: String) {
print("Package received, Press 1 key to proceed")
// Wait for the user input
waitForUserInput() {
input in
if input = 1 {
print("Package delviered to: \(address)")
}
}
}
}
let objectOfSuperType = DeliveryProvider()
let objectOfSubType = ExpressDeliveryProvider()
objectOfSuperType.deliver(to: "Singpoare 11")
objectOfSubType.deliver(to: "Singpoare 11") // Can not replace the Super Class object with subtype means there is some design issue

See Here, Can not replace the Super Class object with subtype means there is some design issue

objectOfSuperType.deliver(to: “Singpoare 11”)
objectOfSubType.deliver(to: “Singpoare 11”)

Because, SUB CLASS not behaving the same way as SUPER CLASS. It is not delivering the package directly. It is awaiting user’s input to proceed.

Interface/Protocol:
Two implementation of the same interface or protocol can be used interchangeably without unexpected behaviour.

Let’s see an example,
There is a web service interface which can be used in the application and also for the unit tests. But in unit tests the FeedService would be mock.

protocol FeedWebService {
// Gets all the feed items and return completion on main thread.
func get(_ urlRequest: URLRequest, completion: @escaping ([Feed]?, Error?) -> Void)
}

Feed Screen which lists all the feeds using the feed service injected into it.

class FeedScreen {
let ws: FeedWebService
init(webservice: FeedWebService) {
self.ws = webService
}
func getAllFeedItems() {
let request = URLRequest..... // URL request to get feed items
ws.get(request) {
feedItems, error in
// got response or error
// Update UI accordinglt
// like tableView.reload etc
}
}
}

Now let’s see how the FeedService can be implemented for real network and mock network and understand what is the violation and how to fix it.

class URLNetwork: FeedWebservice {
func get(_ urlRequest: URLRequest, completion: @escaping ([Feed]?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest.url) {
data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(nil, error)
}
completion(nil, error)
return
}
//. else decode Feed from data and send in completion
//. let feedArray = JSONDecoder().decode...
DispatchQueue.main.async {
completion(feedArray, nil)
}
}
}

Mock Implementation for the Unit Test

class MockNetwork: FeedWebService {
func get(_ urlRequest: URLRequest, completion: @escaping ([Feed]?, Error?) -> Void) {
let feedArray = [Feed(), Feed()]
DispatchQueue.global(qos: .background).async {
completion(feedArray, nil)
}
}
}

So far what we have done is nothing but the OCP, but how to make sure that our OCP is correct or not. Tada, here comes the LSP.

let webService = URLNetwork()
// let mockWebService = MockNetwork()
let feedScreen = FeedScreen(webService: webService)
feedScreen.getAllFeedItems()

Here URLNetwork and MockNetwork are the implementations for the protocol/interface FeedWebService

But for MockNetwork, it is not returning the completion on main thread which we expect as per protocol design
(This is also called the Design By Contract or Design by protocol violation).

Therefore we would not be able to swap this two implementation without changing the code which is LSP violation
If we try to swap webService with mockWebService , it would break the UI operations which expecting main thread.

Read the definition again and it would be more clear after this example, Try!

So to solve this we can make the MockNetwork ‘s implementation to return on main thread only to avoid unexpected behaviour and follow LSP.

See, how LSV allows you to make sure that your OCP is done properly.

Lets move to the next topic Interface Segregation Principle

--

--