SOLID: Open Close Principle(OCP)
When a single change to a program results in a cascade of changes to modules using it, that sound like there is a “bad” design element, leading a program to become fragile, rigid, and un-reusable.
OCP attacks this in a very straightforward way.
An software entity should be open for an extension but close for the modification.
Means, It says that you should design modules that never change.When requirements change, you extend the behaviour of such modules by adding new code, not by changing old code that already works.
For example,
class CoreDataHelper {
func save(_ data: String) {
// Save in Coredata
print("Saved")
}}
class UserManager {
// Not Open For Extension. Tightly coupled
let coreDataHelper = CoreDataHelper()
func save(username: String) {
coreDataHelper.save(username)
}}
let userManager = UserManager()
userManager.save(username: "Mohshin")
In the above example, If we change the CoreDataHelper with SQLDataHelper, UserManager is going to break because it has a tight coupling with CoreDataHelper.
Abstraction is the key
OCP promotes the use of the protocol/interface. Let’s solve the above example by adding an abstraction layer, let’s call it DataManager.
protocol DataManager {
func save(_ data: String)
}
class CoreDataManager: DataManager {
func save(_ data: String) {
// Save in Coredata
print("Saved")
}}
class UserManager {
let dataManager: DataManager
init(datahelper: DataManager) {
self.dataHelper = dataManager
}func save(username: String) {
dataManager.save(username)
}}
/**
/* Adding SQLDataManager without changing the above module code
**/
class SQLDataManager: DataManager {
func save(_ data: String) {
// Save in SQL Database
print("Saved")
}}
let sqlUserManager = UserManager(datamanager: SQLDataManager())
sqlUserManager.save(username: "Mohshin")
Here we have solved it using the Template Pattern but there are other patterns which can come to rescue for example
Decorator pattern, Factory Method, Strategy pattern etc.
The main essence of the OCP is the maintainable code, follow this rule, can surely help to reduce maintenance costs in future and the OPPOSITE also very true.
Another real world example we may have violated OCP could be, Using ENUMs for things which tend to be flexible or supposed to be changed.
Like using enum for FormInput types, for representing tabs,
enum FormInput {
case text
case radio
}
And there are few screens using this (obv switch case), what happens if we introduce a new type ? This all occurrences going to break.
Rather we can do something like this,
struct FormInput {
}
extension FormInput {
static let text = FormInput(type: "text")
static let radio = FormInput(type: "radio")
}
There are so many built in iOS library follows the same
URLSessionComfiguration (default, ephermal), UILayoutPriority, MKDisplayPriority etc
Next is Liskov Substitution Principle