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.

Security Certificates

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