SOLID Principles

SOLID Principles

SOLID principles are an object oriented design concept that are used to design and develop the? scalable, robust and maintainable software.

There are Few important key points:

  • Reduces the Code Rot and makes the code more readable and understandable.
  • Reduces the tight coupling.
  • Improves maintainability .
  • Easy to debug and refactor.
  • Code is reusable.

SOLID Design principle acronym for?

  • Single Responsibility Principle
  • Open Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Single Responsibility Principle:

Single responsibility clearly states that every java class must have only single responsibility. It means that the class contains only single purpose related methods and properties.

If a class contains multiple methods with different responsibilities like registration, login, logger, sendEmail etc. mixing the responsibility in a one class? makes it harder to understand and test, reducing the cohesion, makes it difficult to modify the one part without breaking others and increasing the risk of bugs. So the best way to fix this is to split the class based on functionality, each class has only one responsibility.

class Employee {

public String registration() {...}

public String login() {...}

public void logger() {...}

public boolean sendEmail() {...}

}        

Above eg violates the SRP so to achieve the goal of this principle we separated the classes on the basis of functionality/ responsibility.

Class User{

public String registration() {...}

public String login() {...}

}

Class AppLogger{

public void logger() {...}

}

Class  EmailService{

public boolean sendEmail() {...}

}        

Open Closed Principle :?

Open close responsibility states that Software modules, entities, classes, functions should be open for extension but closed for modification. Open for extension means that you should add new functionality in a new class and? use the existing functionality by extending the existing class.? and closed for modification means that existing code or functionality should? not be allowed to modify. You can achieve this principle by using abstraction such as Interface or abstract classes instead of creating concrete classes.

public class VehicleInfo  

{  

public double vehicleNumber(Vehicle vcl)   

{  

if (vcl instanceof Car)   

{  

return vcl.getNumber();  

if (vcl instanceof Bike)   

{  

return vcl.getNumber();  

}  

}          

Above eg vehicleInfo class has a vehicleNumber method that returns vehicle number. If you want to add one new subclass then get its number so for that you need to add one condition it violates OCR principle. The best way to fix it is to create subclasses and extend vehicleInfo and override that method in subclasses.?

Public class VehicleInfo {

public double vehicleNumber(Vehicle vcl)   {

return vcl.getNumber();

}

}

Public class Car extends VehicleInfo {

public double vehicleNumber(Vehicle vcl)   {

return vcl.getNumber();

}

}

Public class Car extends VehicleInfo {

public double vehicleNumber(Vehicle vcl)   {

return vcl.getNumber();

}

}        

Liskov Substitution Principle:?

The Liskov Substitution Principle states that derived classes must be completely substitutable with their base classes without affecting the functionality of the software. This principle is an extension of OCP and focuses on the behavior of? supertype and its subtype.

Below eg a payment processing system that handles different types of payments like credit card payments and PayPal payments. Define a common interface for all types of payments.

public interface Payment {

    void processPayment(double amount);

}

        

Implement the Payment interface in different payment types.

public class CreditCardPayment implements Payment {

    @Override

    public void processPayment(double amount) {

        // Implementation for processing credit card payment

        System.out.println("Processing credit card payment of $" + amount);

    }

}

public class PayPalPayment implements Payment {

    @Override

    public void processPayment(double amount) {

        // Implementation for processing PayPal payment

        System.out.println("Processing PayPal payment of $" + amount);

    }

}        

create client code that processes payments using the Payment interface. This code should work with any implementation of Payment.?

public class PaymentProcessor {

    public void process(Payment payment, double amount) {

        payment.processPayment(amount);

    }

  }        

If the PaymentProcessor class does not handle this exception, substituting ApplePayPayment for Payment will break the client code and it will violate the LSP.

public class ApplePayPayment implements Payment {

    @Override

    public void processPayment(double amount) {

        // Suppose this implementation throws an exception for negative amounts

        if (amount < 0) {

            throw new IllegalArgumentException("Amount cannot be negative");

        }

        System.out.println("Processing Apple Pay payment of $" + amount);

    }

}        

For fixing this? ensure that ApplePayPayment handles negative amounts appropriately or that the client code is aware of and handles such cases.

public class ApplePayPayment implements Payment {

    @Override

    public void processPayment(double amount) {

        if (amount < 0) {

            // Handle negative amounts appropriately

            System.out.println("Cannot process payment of negative amount: $" + amount);

            return;

        }

        System.out.println("Processing Apple Pay payment of $" + amount);

    }

}        

Interface Segregation Principle:

Interface Segregation Principle states that clients should not be forced to depend on interfaces it does not use. It means that instead of implementing large interfaces, general purpose interfaces,? segregate that large interface into smaller and specific purpose interfaces. Classes implement the functionality specific interface, that makes software flexible and smaller interfaces are easy to maintain and understand. Reduce the tight coupling that makes software robust.

Consider a Document management system where we have different types of documents such as PrintableDocument. Single large interface contains all the operations.The Report class is forced to implement scan and fax methods, which are not applicable. This leads to empty method implementations and violates the ISP.

public interface DocumentOperations {

    void print();

    void scan();

    void fax();

}

public class Report implements DocumentOperations {

    @Override

    public void print() {

        System.out.println("Printing report...");

    }

    @Override

    public void scan() {

        // Not applicable for reports

    }

    @Override

    public void fax() {

        // Not applicable for reports

    }

}        

So correcting this violation by splitting the DocumentOperations interface into smaller, specific interfaces, we can ensure that each class implements only the methods it needs.

Smaller interfaces Printable , Scannable,Faxable with different functionality.

public interface Printable {

    void print();

}

public interface Scannable {

    void scan();

}

public interface Faxable {

    void fax();

}        

Document class? that implements only relevant interfaces.

public class Document implements Printable, Scannable {

    @Override

    public void print() {

        System.out.println("Printing document...");

    }

    @Override

    public void scan() {

        System.out.println("Scanning document...");

    }

}        

Report, Photo and FaxMachine class that implements only relevant interfaces.

public class Report implements Printable {

    @Override

    public void print() {

        System.out.println("Printing report...");

    }

}

public class Photo implements Scannable {

    @Override

    public void scan() {

        System.out.println("Scanning photo...");

    }

}

public class FaxMachine implements Faxable {

    @Override

    public void fax() {

        System.out.println("Faxing document...");

    }

}        

DocumentProcessor uses these interfaces without forcing them to implement the unnecessary methods.

public class DocumentProcessor {

    public void processPrintable(Printable printable) {

        printable.print();

    }

    public void processScannable(Scannable scannable) {

        scannable.scan();

    }

    public void processFaxable(Faxable faxable) {

        faxable.fax();

    }

  }        

Dependency Inversion Principle:

The Dependency Inversion Principle states that high level modules should not depend on low level modules. They should depend on abstraction (Interface and abstraction). Abstraction should not depend on details, details should depend on the abstraction. This principle helps to make it loosely coupled and design becomes easy to change.

Let's consider we need to send notifications via email or SMS. In a tightly coupled design, the high-level class (NotificationService) depends directly on the low-level classes (EmailService and SMSService).

class EmailService {

    public void sendEmail(String message) {

        System.out.println("Sending email: " + message);

    }

}

class SMSService {

    public void sendSMS(String message) {

        System.out.println("Sending SMS: " + message);

    }

}

class NotificationService {

    private EmailService emailService = new EmailService();

    private SMSService smsService = new SMSService();

    public void sendNotification(String message, String medium) {

        if (medium.equals("email")) {

            emailService.sendEmail(message);

        } else if (medium.equals("sms")) {

            smsService.sendSMS(message);

        }

    }

}        

NotificationService is tightly coupled to EmailService? and? SMSService. If we want to add a new notification method requires modifying the NotificationService class. This leads to? violation of the DIP.

To correct the violation of DIP we can refactor the design using abstraction. Creating NotificationSender abstraction that both SMSService and EmailService implement. The NotificationService depends on this abstraction rather than concrete implementations.?

// Abstraction

interface NotificationSender {

    void send(String message);

}

// Low-level modules

class EmailService implements NotificationSender {

    @Override

    public void send(String message) {

        System.out.println("Sending email: " + message);

    }

}

class SMSService implements NotificationSender {

    @Override

    public void send(String message) {

        System.out.println("Sending SMS: " + message);

    }

}

// High-level module

class NotificationService {

    private NotificationSender notificationSender;

    // Constructor injection for the dependency

    public NotificationService(NotificationSender notificationSender) {

        this.notificationSender = notificationSender;

    }

    public void sendNotification(String message) {

        notificationSender.send(message);

    }

}        

It makes code more flexible. We can add a new notification without modifying the notificationSender. Modules become decoupled, high level modules changes are not affected to low level modules and vice-versa.

要查看或添加评论,请登录

AlphaDot Technologies的更多文章

社区洞察

其他会员也浏览了