The SOLID Principles of Object Oriented Programming

The SOLID principles of Object Oriented Programming are a series of design principles invented by the legendardy Robert Martin also known as Uncle Bob.They are a series of principles to be adhered to in order to create code that is easy to maintain and test.These principles are: 1.Single Responsibility Principle 2.Open Closed Principle 3.Liskov Substituability Principle 4.Interface Segregation Principle 5.Dependency Inversion Principle 1.Single Responsibility Principle This states that a class should have only one reason to change. This is done by giving the class a single responsibility and hence the name. The reasoning behind this principle is to create software that is simple to understand, easy to maintain and modify and less error prone as it will be dealing with one responsibility. Code that violates this principle will be hard to maintain, understand and modify. An example of code that violates this principle is illustrated below. Public Class Invoice{ private int id; private String text; public Invoice (int id,String text){ this.id=id; this.text=text; } public int getId() {return id;} public String getText() {return text;} public void setID(int id) {this.id=id;} public void setText(String text){this.text=text;} public void printInvoice(){//code here} } This code has 2 responsibilities.One keeps information about the invoice,the other prints it.If we need to make changes,this could complicate maintainance.To rectify this,we create two classes:one for the invoices,the other for the invoice printing. Public Class Invoice{ private int id; private String text; public Invoice (int id,String text){ this.id=id; this.text=text; } public int getId() {return id;} public String getText() {return text;} public void setID(int id) {this.id=id;} public void setText(String text){this.text=text;} } Public Class InvoicePrinter{ private Invoice invoice; public InvoicePrinter(Invoice invoice){this.Invoice=invoice;} public void printInvoice(){//code here} } The above code separates responsiblities and is easier to maintain. 2.Open/Closed Principle This states that software should be open to extension but closed to modification. In other words, rather than change the source code of existing classes, we should extend them. This ensures that we dont break any of the existing functionality if we change existing classes and leads to code which is simpler to maintain, extend and use.An example of code that violates this principle is illustrated below. class PaymentProcessor { public void process(String method) { if (method.equals("credit")) { // process credit card } else if (method.equals("paypal")) { // process PayPal } else if (method.equals("crypto")) { // process crypto } } } Every time a new payment method is added, we modify this class. That breaks OCP. Instead,we should use this. interface PaymentMethod { void processPayment(); } class CreditCardPayment implements PaymentMethod { public void processPayment() { System.out.println("Processing credit card..."); } } class PayPalPayment implements PaymentMethod { public void processPayment() { System.out.println("Processing PayPal..."); } } class CryptoPayment implements PaymentMethod { public void processPayment() { System.out.println("Processing crypto..."); } } class PaymentProcessor { public void process(PaymentMethod payment) { payment.processPayment(); } } Now we can add new payment types without touching the PaymentProcessor class. You just create a new class implementing PaymentMethod.This allows flexibility and is easy to maintain. 3.Liskov Substitution Principle This states that any instance of a derived class should be substitutable for an instance of its base class without affecting the correctness of the program. In other words, a derived class should behave like its base class in all contexts or we will break existing functionality. Following this principle can lead to code that is easy to maintain. Violating it will lead to a break in existing functionality and unexpected bugs. An example of code that violates this principle is illustrated below. interface Bike { void turnOnEngine(); void accelerate(); } class Motorbike implements Bike { boolean isEngineOn; int speed; @Override public void turnOnEngine() { isEngineOn = true; } @Override public void accelerate() { speed += 5; } } class Bicycle implements Bike { boolean isEngineOn; int speed; @Override public void turnOnEngine() { throw new AssertionError("There is no engine!"); } @Override public void accelerate() { speed += 5; } } In the above example, we use a Bike Interface and 2 classes which implement it. The sec

Apr 14, 2025 - 21:04
 0
The SOLID Principles of Object Oriented Programming

The SOLID principles of Object Oriented Programming are a series of design principles invented by the legendardy Robert Martin also known as Uncle Bob.They are a series of principles to be adhered to in order to create code that is easy to maintain and test.These principles are:
1.Single Responsibility Principle
2.Open Closed Principle
3.Liskov Substituability Principle
4.Interface Segregation Principle
5.Dependency Inversion Principle

1.Single Responsibility Principle
This states that a class should have only one reason to change. This is done by giving the class a single responsibility and hence the name. The reasoning behind this principle is to create software that is simple to understand, easy to maintain and modify and less error prone as it will be dealing with one responsibility. Code that violates this principle will be hard to maintain, understand and modify. An example of code that violates this principle is illustrated below.

 Public Class Invoice{
  private int id;
  private String text;
  public Invoice (int id,String text){
   this.id=id;
   this.text=text; 
  }
  public int getId() {return id;}
  public String getText() {return text;}
  public void setID(int id) {this.id=id;}
  public void setText(String text){this.text=text;}
  public void printInvoice(){//code here}
 }

This code has 2 responsibilities.One keeps information about the invoice,the other prints it.If we need to make changes,this could complicate maintainance.To rectify this,we create two classes:one for the invoices,the other for the invoice printing.

 Public Class Invoice{
  private int id;
  private String text;
  public Invoice (int id,String text){
   this.id=id;
   this.text=text; 
  }
  public int getId() {return id;}
  public String getText() {return text;}
  public void setID(int id) {this.id=id;}
  public void setText(String text){this.text=text;} 
 }

 Public Class InvoicePrinter{
  private Invoice invoice;
  public InvoicePrinter(Invoice invoice){this.Invoice=invoice;}
  public void printInvoice(){//code here}
 }

The above code separates responsiblities and is easier to maintain.

2.Open/Closed Principle
This states that software should be open to extension but closed to modification. In other words, rather than change the source code of existing classes, we should extend them. This ensures that we dont break any of the existing functionality if we change existing classes and leads to code which is simpler to maintain, extend and use.An example of code that violates this principle is illustrated below.

class PaymentProcessor {
    public void process(String method) {
        if (method.equals("credit")) {
            // process credit card
        } else if (method.equals("paypal")) {
            // process PayPal
        } else if (method.equals("crypto")) {
            // process crypto
        }
    }
}

Every time a new payment method is added, we modify this class. That breaks OCP. Instead,we should use this.

interface PaymentMethod {
    void processPayment();
}

class CreditCardPayment implements PaymentMethod {
    public void processPayment() {
        System.out.println("Processing credit card...");
    }
}

class PayPalPayment implements PaymentMethod {
    public void processPayment() {
        System.out.println("Processing PayPal...");
    }
}

class CryptoPayment implements PaymentMethod {
    public void processPayment() {
        System.out.println("Processing crypto...");
    }
}

class PaymentProcessor {
    public void process(PaymentMethod payment) {
        payment.processPayment();
    }
}

Now we can add new payment types without touching the PaymentProcessor class. You just create a new class implementing PaymentMethod.This allows flexibility and is easy to maintain.

3.Liskov Substitution Principle
This states that any instance of a derived class should be substitutable for an instance of its base class without affecting the correctness of the program. In other words, a derived class should behave like its base class in all contexts or we will break existing functionality. Following this principle can lead to code that is easy to maintain. Violating it will lead to a break in existing functionality and unexpected bugs. An example of code that violates this principle is illustrated below.

interface Bike {
    void turnOnEngine();

    void accelerate();
}
class Motorbike implements Bike {

    boolean isEngineOn;
    int speed;

    @Override
    public void turnOnEngine() {
        isEngineOn = true;
    }

    @Override
    public void accelerate() {
        speed += 5;
    }
}
class Bicycle implements Bike {

    boolean isEngineOn;
    int speed;

    @Override
    public void turnOnEngine() {
        throw new AssertionError("There is no engine!");
    }

    @Override
    public void accelerate() {
        speed += 5;
    }
}

In the above example, we use a Bike Interface and 2 classes which implement it. The second class, Bicycle doesn't have an engine and code which references the Bike Interface will throw errors when the turnOnEngine() method is called. If however, we were to use the following method below, no errors will be thrown.

   @Override
    public void turnOnEngine() {
     isEngineOn=true;//substitute human strength for engine
    }

4.Interface Segregation Principle
This states that a client should not be forced to depend on methods that it doesn't use. This principle implies that instead of creating a large interface that covers all the possible methods, it's better to create smaller, more focused interfaces for specific use cases. This approach results in interfaces that are more cohesive and less coupled. An example of code that violates this principle is illustrated below.

interface Worker {
    void work();
    void eat();   // not every worker needs this
}

class Robot implements Worker {
    public void work() {
        System.out.println("Robot working...");
    }

    public void eat() {
        //