Developers are familiar with OOP (Object-Oriented Programming). But understanding OOP Principles Design is another story. In this post, I will show you 7 OOP Principles Design that every developer should master.
1. DRY (Don’t repeat yourself)
Don’t write duplicate code, instead use Abstraction to abstract common things in one place
class BaseActivity(): AppCompatActivity { fun updateNetworkUI() {
isNetworkAvailable(this)
}
}class BaseFragment(): Fragment {
fun updateNetworkUI() {
isNetworkAvailable(context)
}
}
Instead of writing the duplicated code, we can define a function or interface that can expose the network status
fun isNetworkAvailable(context: Context) {
...
}
2. Single Responsibility Principle (SRP)
There should not be more than one reason for a class to change, or a class should always handle single functionality.
// Bad Implementation
data class Store (
private val id: String,
private val name: String,
private val type: String,
...
) {
fun runSaleOff()
fun checkStorage()
fun employ()
fun calculateWeeklySale()
fun calculateMonthlySale()
...
}
For example, If you put more than one functionality in one Class in Kotlin it introduces coupling between two functionality and even if you change one functionality there is a chance you broke coupled functionality, which requires another round of testing to avoid any surprise on the production environment.
3. Open Closed
You should be able to extend a classes behavior, without modifying it.
According to tho this OOP design principle, “Classes, methods or functions should be Open for extension (new functionality) and Closed for modification”.
// Open-Close Principle - Bad example
class GraphicEditor {
public void drawShape(Shape s) {
if (s.m_type==1)
drawRectangle(s);
else if (s.m_type==2)
drawCircle(s);
}
public void drawCircle(Circle r) {....}
public void drawRectangle(Rectangle r) {....}
}
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type=1;
}
}
class Circle extends Shape {
Circle() {
super.m_type=2;
}
}
Below is an example that supports the Open Close Principle. In the new design, we use the abstract draw() method in GraphicEditor for drawing objects, while moving the implementation in the concrete shape objects
// Open-Close Principle - Good example
class GraphicEditor {
public void drawShape(Shape s) {
s.draw();
}
}
class Shape {
abstract void draw();
}
class Rectangle extends Shape {
public void draw() {
// draw the rectangle
}
}
4. Liskov Substitution Principle
If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.
Let’s we have the following code
// Bad Implementation
abstract class Store (...) {
fun isOpenning(): Boolean
fun printFavoriteBook() {}
}class ClothingStore(): Store {
override fun isOpenning(): Boolean {}
}
The clothing store can’t replace the Store with printFavoriteBook. So we need to fix it.
abstract class Store (...) {
fun isOpenning(): Boolean
}abstrace class BookStore... {
fun printFavoriteBook()
}class ClothingStore(): Store {
override fun isOpenning(): Boolean {}
}
5. Dependency Injection or Inversion principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
- Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
// The NetworkRepository is depend on AuthSecurityPref constructorclass NetworkRepository (...) {
val authSecurityPref = AuthSecurityPref(...)
}
Fix it
class NetworkRepository (authSecurityPref: AuthSecurityPref)
6. Interface Segregation Principle
No client should be forced to depend on methods it does not use
// interface segregation principle - bad example
interface IWorker {
public void work();
public void eat();
}
class Worker implements IWorker{
public void work() {
// ....working
}
public void eat() {
// ...... eating in launch break
}
}
By splitting the IWorker interface in 2 different interfaces the new Robot class is no longer forced to implement the eat method
// interface segregation principle - good example
interface IWorker extends Feedable, Workable {
}
interface IWorkable {
public void work();
}
interface IFeedable{
public void eat();
}
class Robot implements IWorkable{
public void work() {
// ....working
}
}
class Robot implements IWorkable{
public void work() {
// ....working
}
}
7. Delegation
The Delegation pattern has proven to be a good alternative to implementation inheritance
interface Base {
fun print()
}class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}class Derived(b: Base) : Base by bfun main() {
val b = BaseImpl(10)
Derived(b).print()
}