Thursday, July 27, 2023

SOLID Principles

The SOLID principles are a set of five object-oriented design principles that aim to make software designs more maintainable, scalable, and flexible. Each principle focuses on a specific aspect of good design and encourages developers to write code that is easy to understand and change. Let's understand each SOLID principle with a simple example:

  1. Single Responsibility Principle (SRP):

  2. A class should have only one reason to change, i.e., it should have a single responsibility.

    Example:







    class Employee {

    void calculateSalary() {

    // Code to calculate salary

    }


    void saveEmployeeData() {

    // Code to save employee data to the database

    }


    void generateReport() {

    // Code to generate an employee report

    }

    }

    In this example, the Employee class is handling three distinct responsibilities: calculating salary, saving data, and generating reports. Instead, we should separate these responsibilities into different classes.


  3. // Good Design - Separate classes for each responsibility class Employee { void calculateSalary() { // Code to calculate salary } } class EmployeeDataRepository { void saveEmployeeData() { // Code to save employee data to the database } } class EmployeeReportGenerator { void generateReport() { // Code to generate an employee report } }
  1. Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification.

    Example: // Bad Design - Modification needed to add a new shape class AreaCalculator { double calculateArea(Shape shape) { if (shape instanceof Circle) { Circle circle = (Circle) shape; // Code to calculate area of circle } else if (shape instanceof Rectangle) { Rectangle rectangle = (Rectangle) shape; // Code to calculate area of rectangle } else { // Code to calculate area of other shapes } } }

    In this example, to add a new shape (e.g., Triangle), we need to modify the AreaCalculator class. This violates the OCP. Instead, we can use polymorphism and abstract classes/interfaces to extend the functionality. // Good Design - Open for extension, closed for modification interface Shape { double calculateArea(); } class Circle implements Shape { // Implement calculateArea for circle } class Rectangle implements Shape { // Implement calculateArea for rectangle } // Adding a new shape (e.g., Triangle) does not require modifying existing code class Triangle implements Shape { // Implement calculateArea for triangle }


  1. Liskov Substitution Principle (LSP):

  2. Subtypes (derived classes) should be substitutable for their base types (parent classes).

    Example: // Bad Design - Subtypes not substitutable class Bird { void fly() { // Code for flying } } class Penguin extends Bird { // Penguins cannot fly @Override void fly() { throw new UnsupportedOperationException("Penguins cannot fly."); } }

    In this example, the Penguin class violates LSP as it throws an exception for the fly method, which is not expected behavior for a subtype of Bird. Instead, we should design the hierarchy to ensure substitutability.

  3. // Good Design - Subtypes are substitutable interface Bird { void fly(); } class Sparrow implements Bird { @Override void fly() { // Code for flying like a sparrow } } class Penguin implements Bird { @Override void fly() { // Penguins cannot fly, but the method is still defined System.out.println("Penguins cannot fly."); } }

  4. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use. Keep interfaces small and focused.

    Example: // Bad Design - A large interface forcing clients to implement unnecessary methods interface Printer { void print(); void scan(); void fax(); } class LaserPrinter implements Printer { // Need to implement all three methods, even though LaserPrinter cannot scan and fax }

    In this example, the Printer interface is too large and forces clients to implement unnecessary methods. Instead, we should create smaller interfaces.


  5. //Good Design - Smaller interfaces for specific functionality interface Printer { void print(); } interface Scanner { void scan(); } interface FaxMachine { void fax(); } class LaserPrinter implements Printer { // Only need to implement print() for LaserPrinter }

  6. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions.

instead of directly depending on concrete classes, depend on interfaces or abstract classes. This makes your code more flexible and maintainable.

No comments:

Security Certificates

  1. Cryptography Basics Understand Key Concepts : Encryption, decryption, hashing, and digital signatures. Key terms: confidentiality, inte...