Architecture

[SOLID] Open-Closed principle explained

I struggled with my algorithm problems in front of me on my desk for nearly two hours. It sucks “Am I too old doing all of this ?” – I thought, so I decided to temporary stop ! And then I saw an OOP programming book that I was about to read early of today (it’s 9:17 PM now) then I opened it, I read it and then I want to write something about what I’ve read so that’s how this post is published.

This article is about one of the five principles of SOLID – Open-Closed principle. A very important principle that you need to understand (actually all the five principles are equally important lolz) but I thinks this principle is easiest to understand and apply. I’ll try my best to explain to you and because i’m a iOS engineer so examples in this post are related to iOS environment but I think it’s not something that prevent you from understanding so hopefully you’ll take something home after this post. Let’s get started.

Definition

Software entities (classes, modules, functions, etc.) should be open for extensions, but closed for modification.

– Wikipedia

I like the definition, short and to the point. It has two primary ideas so let’s take a look at them.

Open for extensions

Software is ‘soft’ which is it’s always change, if it doesn’t you don’t need to learn all of the programming principles then. But it does and always does. So ‘Open for extensions’ means that as requirement changes, module’s behaviors can be extended to satisfy those changes. Let’s see a violation example:

The first violation prompted due to lack of responsibility in the use of frameworks. Logging for instance, let’s say in the first phase of the development process, your client want you to use Firebase Analytics to track screen events. Naively, you call Analytics.logEvent(ScreenView, parameters: []) in all the screens of the app. It works fine until one day your boss come and say your client don’t want to replace Firebase Analytics with another Analytics framework. You know what you need to do then…

So clearly your logging feature violates OCP. It doesn’t encourage changes, put another way, you need to do a lot of work to make a change which from the business guy’s eyes – it’s very easy.

Closed for modification

The next attribute means that extending the behavior of a module does not result in changes to the existing code in the module. How is it possible ? – I’m glad you ask. Let’s see another violation example then we will find out how it is absolutely possible:

Firstly If I have a table view controller and want you to fetch data by calling an API, how would you implement it ? Do you have something like following:

class TableViewController: UITableViewController {
   func viewDidLoad() {
      APIService.shared.loadFeed() {// completion handler}
   }
}

It is familiar, right ? So far so good. Now I have a new requirement (no surprised) in order to improve user experience I want you to load the feed from different endpoints based on which type of network source that user are using (3G, 4G, 5G) Now what would you do to satisfy the new requirement ? Do you (again) have something like following:

class TableViewController: UITableViewController {
   func viewDidLoad() {
      if NetworkConditioner.is5G {
         APIService.shared.loadFeed5G()
      } else if NetworkConditioner.is4G {
         APIService.shared.loadFeed4G()
      } else { // 3G 
         APIService.shared.loadFeed() {// completion handler}
      }
   }
}

Do you smell something ? Yes it’s from your code. the TableViewController is not closed for modifications. Every time we have a requirement change (e.g new type of network source) we need to add a new if-else case.

Every time I see a switch case statement inside a business logic component I stop and re-think about the design.

Okay. We walked through the definition and dive deep into it by some common violations we can find in many codebases. Now let’s move on to learn a technique that you can apply to satisfy OCP.

Abstraction is the Key

The abstractions are abstract base classes (in Swift we use Protocol instead). A abstraction acts as a boundary between client and its collaboration. The client now depends on the abstraction and the implementation detail conforms to the abstraction.

That way, the dependency are now reverted. Instead of letting Client depends a concrete implementation detail, client is now only talk to an abstraction. So that, we free to change behavior from outside of the client. For example, we can create many implementation details as we want without changing a line of code in the Client.

Let’s try to solve one of the two violation examples we mentioned earlier – the logging one. Instead of letting view controllers talk directly to the Firebase Analytics as we did, we are now create a boundary between them.

By doing that we are now free to use any Analytics we (or client) want without bother existing code. One thing to note here is the hardest thing in order satisfy OCP is to create a good-enough abstraction, we don’t want the abstract to be too abstract just to handle some future requirements that might never come but at the same time we it need to abstract enough for the implement details conforms to. Don’t worry, by practice and failure and repeat you’ll learn how to do it efficiently.

Conclusion

I hope this post was useful for you to understand the the Open-Closed principle. And you did get something home with you. Thanks for the reading.

If you like this post please share it as a contribution for me. If you have any question, feel free to comment down bellow or if you want to contact directly don’t forget I leave my email address in the About Me page at the top left of this website. Goodbye, See you in the next post.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *