What is SOLID principles

SOLID is a mnemon­ic for five design prin­ci­ples intend­ed to make soft­ware designs more under­stand­able, flex­i­ble and maintainable.

As with every­thing in life, using these prin­ci­ples mind­less­ly can cause more harm than good. The cost of apply­ing these prin­ci­ples into a pro­gram’s archi­tec­ture might be mak­ing it more com­pli­cat­ed than it should be. I doubt that there’s a suc­cess­ful soft­ware prod­uct in which all of these prin­ci­ples are applied at the same time. Striv­ing for these prin­ci­ples is good, but always try to be prag­mat­ic and don’t take every­thing writ­ten here as dogma.

SOLID first principle: Single Responsibility Principle

A class should have just one rea­son to change.
Try to make every class respon­si­ble for a sin­gle part of the func­tion­al­i­ty pro­vid­ed by the soft­ware, and make that respon­si­bil­i­ty entire­ly encap­su­lat­ed by (you can also say hid­den with­in) the class

SOLID second principle: Open/Closed Principle

Class­es should be open for exten­sion but closed for modification.
The main idea of this prin­ci­ple is to keep exist­ing code from break­ing when you imple­ment new features.

A class is open if you can extend it, pro­duce a sub­class and do what­ev­er you want with it—add new meth­ods or fields, over­ride base behav­ior, etc. Some pro­gram­ming lan­guages let you restrict fur­ther exten­sion of a class with spe­cial key­words, such as final. After this, the class is no longer open. At the same time, the class is closed (you can also say com­plete) if it’s 100% ready to be used by other class­es—its inter­face is clear­ly defined and won’t be changed in the future.

SOLID third principle: Liskov Sub­sti­tu­tion Prin­ci­ple

When extend­ing a class, remem­ber that you should be able to pass objects of the sub­class in place of objects of the par­ent class with­out break­ing the client code.
This means that the sub­class should remain com­pat­i­ble with the behav­ior of the super­class. When over­rid­ing a method, extend the base behav­ior rather than replac­ing it with some­thing else entirely.

The sub­sti­tu­tion prin­ci­ple is a set of checks that help pre­dict whether a sub­class remains com­pat­i­ble with the code that was able to work with objects of the super­class. This con­cept is crit­i­cal when devel­op­ing libraries and frame­works because your class­es are going to be used by other peo­ple whose code you can’t direct­ly access and change.

SOLID fourth principle: Interface Segregation Principle

Clients shouldn’t be forced to depend on meth­ods they do not use.
Try to make your inter­faces nar­row enough that client class­es don’t have to imple­ment behav­iors they don’t need.

Accord­ing to the inter­face seg­re­ga­tion prin­ci­ple, you should break down “fat” inter­faces into more gran­u­lar and spe­cif­ic ones. Clients should imple­ment only those meth­ods that they real­ly need. Oth­er­wise, a change to a “fat” inter­face would break even clients that don’t use the changed methods.

Class inher­i­tance lets a class have just one super­class, but it doesn’t limit the num­ber of inter­faces that the class can imple­ment at the same time. Hence, there’s no need to cram tons of unre­lat­ed meth­ods to a sin­gle inter­face. Break it down into sev­er­al more refined inter­faces—you can imple­ment them all in a sin­gle class if need­ed. How­ev­er, some class­es may be fine with imple­ment­ing just one of them.

SOLID fifth principle: Dependency Inversion Principle

High-level class­es shouldn’t depend on low-level class­es. Both should depend on abstrac­tions. Abstrac­tions shouldn’t depend on details. Details should depend on abstractions.
Usu­al­ly when design­ing soft­ware, you can make a dis­tinc­tion between two lev­els of classes.

Low-level class­es imple­ment basic oper­a­tions such as work­ing with a disk, trans­fer­ring data over a net­work, con­nect­ing to a data­base, etc.
High-level class­es con­tain com­plex busi­ness logic that directs low-level class­es to do something.

Some­times peo­ple design low-level class­es first and only then start work­ing on high-level ones. This is very com­mon when you start devel­op­ing a pro­to­type on a new sys­tem, and you’re not even sure what’s pos­si­ble at the high­er level because low-level stuff isn’t yet imple­ment­ed or clear. With such an approach busi­ness logic class­es tend to become depen­dent on prim­i­tive low-level classes.

The depen­den­cy inver­sion prin­ci­ple sug­gests chang­ing the direc­tion of this dependency.

For starters, you need to describe inter­faces for low-level oper­a­tions that high-level class­es rely on, prefer­ably in busi­ness terms. For instance, busi­ness logic should call a method openReport(file) rather than a series of meth­ods openFile(x), readBytes(n), closeFile(x). These inter­faces count as high-level ones.
Now you can make high-level class­es depen­dent on those inter­faces, instead of on con­crete low-level class­es. This depen­den­cy will be much soft­er than the orig­i­nal one.
Once low-level class­es imple­ment these inter­faces, they become depen­dent on the busi­ness logic level, revers­ing the direc­tion of the orig­i­nal dependency.
The depen­den­cy inver­sion prin­ci­ple often goes along with the open/closed prin­ci­ple: you can extend low-level class­es to use with dif­fer­ent

REF

https://barryvanveen.nl/articles/51-8-resources-to-learn-about-solid-design-principles

About the Author

Trần Huy

View all author's posts

Bài viết khác

Build for global scale: AFK scale cube and basic rule to build an application for global scale

REF https://akfpartners.com/growth-blog/scale-cube

Clean code in writing Go program

When writing code in Go, it is important to follow good coding practices to ensure that your code is clean, maintainable, and scalable. Here are some clean code and clean architecture practices to follow: Go coding style: Follow the official Go coding style guide, which includes recommendations for naming conventions, formatting, and documentation. Consistent coding […]

Interfaces in Go and best practice

Interfaces in Go are a set of methods that defines a behavior. A type can implement an interface by defining methods with the same signatures as the methods defined in the interface. This allows for a form of polymorphism in Go, where a single function or method can operate on values of different types, as […]

Basic concepts of Protocol-Oriented Programming (POP) in swift and ios developer

Protocol-Oriented Programming (POP) is a programming paradigm introduced in Swift that emphasizes the use of protocols as a way to define and enforce common behavior for multiple types. POP is a powerful tool for designing and organizing code, and can be used to achieve many of the same goals as object-oriented programming, but with greater […]

Functional Reactive Programming (FRP) and Imperative Programming :which one to use?

the big idea about Functional Reactive Programming (FRP) and Imperative Programming : which one to use? The choice between using Functional Reactive Programming (FRP) and Imperative Programming often depends on the particular problem being solved and the specific requirements of the project. Here are some general guidelines for when to use FRP and when to […]

Functional Reactive Programming (FRP) for ios developer

Functional Reactive Programming (FRP) is a programming paradigm that has gained popularity among iOS developers due to its ability to handle asynchronous events and its emphasis on immutability, composability, and purity. FRP allows iOS developers to write clean, maintainable code that is easy to understand and to scale. In traditional iOS development, handling asynchronous events […]