Showing posts with label Tutorial. Show all posts
Showing posts with label Tutorial. Show all posts

Saturday, June 28, 2025

Interface Segregation Principle(ISP) in Java

java,programming,software development,technology,design principle
Recently, I came across a large interface with many methods. I wonder why a class need to implement these methods which are not related to it. Fortunately, while searching for a solution, I discovered the Interface Segregation Principle. It is one of five SOLID principles in object-oriented design.
SOLID Design Principles in Java

This blog post reviews the Interface Segregation Principle (ISP) and shows how it can be applied using Java examples.

Interface Segregation Principle

It tells us:
Clients should not be force to depend upon interfaces that they do not use.
It means we shouldn't make our interfaces bigger. We should not pollute our interfaces with irrelevant methods. And force the classes to implement methods they don't care about. Make smaller, more specific interfaces instead of one big one that does everything. To help you understand this better, let's look at the busy world of a restaurant.

Bad Design: Violates ISP

Consider the situation: we are creating software to control how a restaurant works. Perhaps our first thought is to design a single interface for every employee:
Now, let's consider our staff roles: a Chef, a Waiter.

The Chef class implements the IRestaurant interface.
Interface Segregation Principle(ISP) in Java

Waiter class also implements the IRestaurant interface.
Interface Segregation Principle(ISP) in Java
As we can see, both the Chef and Waiter classes implement the IRestaurant interface. But we are forcing the Chef to take on all of the restaurant's responsibilities. The Chef class implements methods such as takeOrder() and washDishes(), which are not part of their job description. This is also true of the Waiter. The Waiter class is also required to include methods such as cookDish() and washDishes(). Here we end up with UnsupportedOperationException or empty method bodies.

It is making our code:
  • Bloated: In our Chef and Waiter class, there are some unnecessary methods.
  • Confusing: It is not immediately clear what a Chef or Waiter does.
  • Fragile: Any change to the IRestaurant interface would cause the Chef and Waiter classes to change, even if it was irrelevant to them.

Small, Specific Interfaces: Now we are on the right path

So, according to the ISP, each interface should be responsible for a specific set of tasks. We must create multiple, smaller interfaces with related responsibilities.

Let's refactor the IRestaurant interface. We built two interfaces, ICook and IWaiter.

So Chef implements the ICook interface.
Interface Segregation Principle(ISP) in Java

And Waiter implements the IWaiter interface.
Interface Segregation Principle(ISP) in Java

The Benefits of ISP

  • Clarity and Readability: Each class now only includes methods that are directly relevant to its role.
  • Reduced Coupling: Our classes are no longer reliant on methods they don't employ. Changes to one interface will not cause unrelated classes to change.
  • Increased Flexibility: If we add a new role, the existing interfaces will not be affected.

The Interface Segregation Principle is an effective tool for creating robust and maintainable Java applications. With these monolithic interfaces, we can create Java applications that are more flexible and easier to understand.

Happy coding!!! 😊
in

Sunday, June 22, 2025

Liskov Substitution Principle (LSP) in Java

java,programming,software development,technology,design principle
The SOLID principles are a set of five design principles that enhances object-oriented programming (OOP). This blog post reviews the Liskov Substitution Principle(LSP) and shows how it can be applied using Java examples.

SOLID Design Principle is made up of five different principles:
  • S: Single Responsibility Principle(SRP)
  • OOpen-Closed Principle(OCP)
  • LLiskov Substitution Principle(LSP)
  • IInterface Segregation Principle(ISP)
  • DDependency Inversion Principle DIP)
SOLID Design Principles in Java

Liskov Substitution Principle

The Liskov Substitution Principle (LSP) emphasizes that objects from a superclass can be replaced with objects from a subclass without affecting the program's correctness. This principle ensures that derived classes follow the contract established by their base classes, thereby improving code reliability and preventing unexpected behavior in polymorphic scenarios.

This is the third principle of the SOLID design principle group. It tells us:
We can replace subclass or child class objects with base class objects whenever necessary. This substitution should not change the program's desired properties.
Let us demonstrate this with a simple example. Assume we have the class Bird, which has two methods: eat() and fly(). Class Eagle has now inherited the Bird class. The Eagle class now owns the eat() and fly() methods. So we can use the object of the Bird class instead of the object of the Eagle class. This will not disrupt the existing functionalities.
Liskov Substitution Principle (LSP) in Java
It sounds simple, and it is. But there's a catch here. This principle applies to classes that exhibit the same behavior. We assume that the Bird and Eagle classes behave similarly. At the same time, the subclass's overridden methods should behave the same way as the superclass's methods. Based on this assumption, we can use the Bird object rather than the Eagle object.

But what happens if the parent and child classes do not behave similarly? What happens if the overridden methods of the subclass do not behave like the superclass's methods. How can we apply the Liskov Substitution Principle in those cases?

Let us consider another example. We will explore this principle with a Java-based Payment Processing System.

Example without LSP

Assume an e-commerce platform has different types of payment methods. Customers can pay with Credit cards, Debit cards, PayPal, or Cash.
The payment classes override the processPayment(...) method. This method will contain logic for processing payments. We may need to consume third-party APIs to settle Credit card or PayPal payments.

But in the case of cash payments, there is no need to process. We can simply accept the cash. As a result, the behavior of precessPayment(...) differs when cash payments are made.
Liskov Substitution Principle (LSP) in Java
The behavior of the processPayment(...) method is not the same in all the subclasses. So this violates the Liskov Substitution Principle.


Example with LSP

To implement the Liskov Substitution Principle, we created two interfaces: IPayment and IOnlinePayment.
IPayment is the interface for all payment types. IOnlinePayment is the interface for all kinds of online payments.

CreditCardPayment and PayPalPayment have implemented the IOnlinePayment interface.
We've overridden the authenticate() and processPayment() methods in the classes.

Here is our class on cash payments. This class only implements the IPayment interface.
It overrides the processPayment() method. Because authentication is not required here, we have not implemented the IOnlinePayment interface.
Liskov Substitution Principle (LSP) in Java
Every class correctly extends Payment's behavior while adhering to expectations.

Happy coding!!! 😊
in

Wednesday, February 19, 2025

Open-Closed Principle (OCP)

java,programming,software development,technology,design principle
The SOLID principles, a set of five design principles that enhance object-oriented programming (OOP). We discussed the Single Responsibility Principle (SRP) in this article.

SOLID Design Principle is made up of five different principles:
  • S: Single Responsibility Principle (SRP)
  • OOpen-Closed Principle (OCP)
  • LLiskov Substitution Principle (LSP)
  • IInterface Segregation Principle (ISP)
  • DDependency Inversion Principle (DIP)
SOLID Design Principles in Java
This blog post reviews the Open-Closed Principle (OCP) and shows how it can be applied using Java examples.

Open-Closed Principle

This is the second principle of the SOLID design principle group. It tells us:
Classes or modules should be open for extension but closed for modification.
It states that we should not modify any existing classes or modules. Rather, we should create a new entity by inheriting the properties of the existing classes and modules. So, this principle tells us to use inheritance and overriding to extend existing behaviors.

Example without OCP

Assume an e-commerce platform has two discount types: flat discount and percentage discount. Here's our discount system. As you can see, the calculateDiscount(...) method is used to compute a discount on the purchase price based on the discount type.
public class DiscountSystem {
    private Double flatDiscountAmount;
    private Double discountPercentage;
    private Double baseDiscountPercentage;

    // CALCULATE DISCOUNT
    public Double calculateDiscount(EDiscountType type, Double amount) {
        Double discountedAmount;

        switch (type.name()) {
            case "FLAT":
                discountedAmount = amount - (amount * baseDiscountPercentage / 100) - flatDiscountAmount;
                break;
            case "PERCENTAGE":
                discountedAmount = amount - (amount * baseDiscountPercentage / 100)
                        - (amount * discountPercentage / 100);
                break;
            default:
                discountedAmount = 0D;
                break;
        }
        return discountedAmount;
    }

    public Double getFlatDiscountAmount() {
        return flatDiscountAmount;
    }

    public DiscountSystem setFlatDiscountAmount(Double flatDiscountAmount) {
        this.flatDiscountAmount = flatDiscountAmount;
        return this;
    }

    public Double getDiscountPercentage() {
        return discountPercentage;
    }

    public DiscountSystem setDiscountPercentage(Double discountPercentage) {
        this.discountPercentage = discountPercentage;
        return this;
    }

    public Double getBaseDiscountPercentage() {
        return baseDiscountPercentage;
    }

    public DiscountSystem setBaseDiscountPercentage(Double baseDiscountPercentage) {
        this.baseDiscountPercentage = baseDiscountPercentage;
        return this;
    }
}

public enum EDiscountType {
    FLAT, PERCENTAGE;
}
Assume the e-commerce platform wants to introduce another discount system. So first, we'll create a new discount type. Then, create a new case block to implement the new discount logic. After a few days, they plan to introduce another new discount system. So, once again, we must take the same steps to implement the new discount logic.

Every time, we modify the existing code to add new business logic. As a result, these frequent changes increase the risk of breaking existing functionality. This is what the Open-Closed Principle (OCP) says to avoid.

Example with OCP

To implement OCP, we must first define an abstract class with an abstract method. Here's our Discount abstract class. This abstract class has its own property and an abstract method called calculateDiscount(...). This abstract method will be overridden in the class that extends the Discount class.
public abstract class Discount {
    private Double baseDiscountPercentage;

    public abstract Double calculateDiscount(Double amount);

    public Double getBaseDiscountPercentage() {
        return baseDiscountPercentage;
    }

    public void setBaseDiscountPercentage(Double discountPercentage) {
        this.baseDiscountPercentage = discountPercentage;
    }
}

The code snippet below is for FlatDiscountService, which extends the Discount class. It also overrides the calculateDiscount(...) method to incorporate its own discount business logic.
public class FlatDiscountService extends Discount {
    private Double flatDiscountAmount;

    public Double getFlatDiscountAmount() {
        return flatDiscountAmount;
    }

    public FlatDiscountService setFlatDiscountAmount(Double discountAmount) {
        this.flatDiscountAmount = discountAmount;
        return this;
    }

    @Override
    public Double calculateDiscount(Double amount) {
        return amount - (amount * super.getBaseDiscountPercentage() / 100) - flatDiscountAmount;
    }
}

We can develop another new discount service. This is PercentageDiscountService that uses the Discount class.
public class PercentageDiscountService extends Discount {
    private Double discountPercentage;

    public Double getDiscountPercentage() {
        return discountPercentage;
    }

    public PercentageDiscountService setDiscountPercentage(Double discountPercentage) {
        this.discountPercentage = discountPercentage;
        return this;
    }

    @Override
    public Double calculateDiscount(Double amount) {
        return amount - (amount * super.getBaseDiscountPercentage() / 100) - (amount * discountPercentage / 100);
    }
}
Open-Closed Principle (OCP)
If the e-commerce platform wants to introduce a new discount system, it simply creates a new class by extending the abstract one. At the same time, our current discount system will remain unchanged.

Here is our test class to test the discount services.
public class OcpGoodExampleMain {
    public static void main(String[] args) {
        Double purchasedAmount = 4710.78;

        FlatDiscountService flatDiscountService = new FlatDiscountService()
                .setFlatDiscountAmount(250.98);
        flatDiscountService.setBaseDiscountPercentage(1D);
        System.out.println("Actual purchased amount: " + purchasedAmount);
        System.out.println("After flat discount: " + flatDiscountService.calculateDiscount(purchasedAmount));

        System.out.println("-------------------------------------");

        purchasedAmount = 7980.98;
        PercentageDiscountService percentageDiscountService = new PercentageDiscountService()
                .setDiscountPercentage(3.6);
        percentageDiscountService.setBaseDiscountPercentage(1.7);
        System.out.println("Actual purchased amount: " + purchasedAmount);
        System.out
                .println("After percentage discount: " + percentageDiscountService.calculateDiscount(purchasedAmount));
    }
}

Using the Open-Closed Principle (COP), new discount types can be added without changing the Discount class. When we add new discount strategies, the core system is left untouched.

The Open-Closed Principle (OCP) can be applied in various real-world software development scenarios. In those cases, we need to add functionality without changing the existing code. Here are some real-world examples.
  1. Payment Systems: Credit card payments are initially accepted by an online store, but PayPal, Apple Pay, and cryptocurrency payments must be added later.
  2. Logging System: An application begins by logging into a text file, but it must eventually support logging into databases, cloud services (such as AWS CloudWatch), or external monitoring tools.
  3. Notification Services: A notification system sends email notifications at first, but it must eventually support SMS, push notifications, and WhatsApp messages.
  4. User Role Management: A web application has an Admin role, but additional roles such as Editor, Viewer, and Moderator with varying permissions must be added.
  5. Machine Learning Model Deployment: A system begins with a simple linear regression model but eventually needs to support deep learning models, decision trees, and ensemble methods.

Happy coding!!! 😊
in

Sunday, February 09, 2025

SOLID Design Principles in Java

java,programming,software development,technology,design principle
Writing clean, maintainable, and scalable code is essential in software development. The SOLID principles, a set of five design principles that enhance object-oriented programming (OOP), are one of the most effective ways to achieve this.

While writing Java code, we should follow the SOLID principle to maintain a better software architecture. This principle reduces code complexity and makes our code more testable.

The five different principles are:
  • S: Single Responsibility Principle (SRP)
  • OOpen-Closed Principle (OCP)
  • LLiskov Substitution Principle (LSP)
  • IInterface Segregation Principle (ISP)
  • DDependency Inversion Principle (DIP)
SOLID Design Principles in Java
This blog post reviews the Single Responsibility Principle (SRP) and shows how it can be applied using Java examples.

Assume a restaurant is run by a single man. He is solely in charge of cooking, selling, shopping, cleaning, and accounting. This allows him to run his restaurant for a limited time. However, over time, he may close his business due to exhaustion and inefficiency.
SOLID Design Principles in Java
So, what will he do to save his business? He ought to recruit help and delegate various tasks to different people.

Single Responsibility Principle (SRP)

This is the first principle of the Solid Design Principles group. It tells us:
There should be no more than one reason for a class to change.
The statement may appear a little complicated. However, this is not the case. A single responsibility indicates that a class should provide or address a specific functionality. A class with multiple responsibilities is subject to several changes.

Consider an image of a neatly organized toolbox. Each tool in this toolbox serves a specific purpose. A wrench is used for tightening bolts, and a hammer for hammering nails. Similarly, each class should have a single, well-defined function in software design.
SOLID Principles In Java

Let us look at a simple Java example:
public class UserService {

	// REGISTER A NEW USER
	public String register(UserDetails registerDetails) {
		System.out.println("User registration is completed successfully.");
		return "SUCCESS";
	}

	// UPDATE USER DETAILS
	public String updateUserDetails(UserDetails details) {
		System.out.println("User details is updated successfully.");
		return "SUCCESS";
	}

	// LOGIN USING USERNAME AND PASSWORD
	public String login(String userName, String password) {
		System.out.println("User credential is verified successfully.");
		return "SUCCESS";
	}

	// SEND OTP TO USER PHONE NUMBER
	public String sendOTP(String phoneNumber) {
		// CONSUME THIRD PARTY API TO SEND OTP VIA SMS
		// ..

		System.out.println("OTP is successfully sent.");
		return "SUCCESS";
	}

	// OTP VALIDATION
	public String validateOTP(String code) {
		// OTP/CODE VALIDATION LOGIC
		// ..

		System.out.println("OTP validation is completed successfully.");
		return "SUCCESS";
	}

	// SSO AUTHENTICATION: TOKEN VALIDATION
	public String validateOAuthToken(String token) {
		// CONSUME THIRD PARTY API TO VALIDATE OAUTH TOKEN
		// ..

		System.out.println("OAurh Token validation is completed successfully.");
		return "SUCCESS";
	}

	// SAVE AUDIT DETAILS - MAINTAIN A LOG FOR THE CHANGES OF USER DETAILS
	public String saveAudit(UserRegisterDetails details) {
		System.out.println("Audit information of user details is saved successfully.");
		return "SUCCESS";
	}
}
We can see that the UserService has methods for:
  • Register a new User
  • Update user details
  • User login using credentials
  • Send OTP to the user's phone number
  • OTP validation
  • OAuth token validation
  • Save audit information - changes in user details

We implemented several functionalities in this class. However, there are many reasons why this class may change. What are some possible reasons for the change in this UserService class?
  • If we add a new property to the UserDetails class, we must modify the register(...) and updateUserDetails(...) methods.
  • To send OTP, we must use third-party SMS API. If the logic for consumption in the SMS API changes, we should modify the sendOTP(...) method.
  • If the logic for OTP validation changes, we must update the validateOTP(..) method.
  • Assume we have implemented Office 365 SSO, which allows users to log in to our application using their Office 365 accounts. We now want users with Google and Github accounts to be able to log in to our application using SSO. So, once again, we must change the validateOAuthToken(...) method.
  • If we want to save audit information other than user details, we must modify the saveAudit(...) method.

So, as you can see, there are several reasons why our class will change. And that is what we should avoid. This is what the Single Responsibility Principle (SRP) says to avoid.

In the software development process, we can not avoid changes in our code. So, whenever there are changes, we should create a separate class or module to handle those responsibilities. This allows us to change our code in an organized manner.

So, when designing a class or module, remember that a class addresses a specific concern. If a change request is received for that class, there can only be one reason for it to change.

To follow the SRP, we divided the responsibilities of the UserService class into multiple classes.

For example, the UserRegistrationService is responsible for registering new user details:
public class UserRegistrationService {

    // REGISTER A NEW USER
    public String register(UserDetails registerDetails) {
        // LOGIC TO SAVE USER DETAILS IN DB
        // ...

        System.out.println("User registration is completed successfully.");
        return "SUCCESS";
    }
}

The UserDetailsUpdateService is responsible for updating existing user details:
public class UserDetailsUpdateService {

    // UPDATE USER DETAILS
    public String updateUserDetails(UserDetails details) {
        // LOGIC TO SAVE/UPDATE USER DETAILS IN DB
        // ...

        System.out.println("User details is updated successfully.");
        return "SUCCESS";
    }
}

In OTPService, we have methods to send and validate OTP.
public class OTPService {

	// SEND OTP TO USER PHONE NUMBER
	public String sendOTP(String phoneNumber) {
		// CONSUME THIRD PARTY API TO SEND OTP VIA SMS
		// ..

		System.out.println("OTP is successfully sent.");
		return "SUCCESS";
	}

	// OTP VALIDATION
	public String validateOTP(String code) {
		// CODE VALIDATION LOGIC
		// ..

		System.out.println("OTP validation is completed successfully.");
		return "SUCCESS";
	}
}

The AuditService class saves audit details.
public class AuditService {

    // SAVE AUDIT DETAILS - MAINTAIN A LOG FOR THE CHANGES IN THE ENTITIES
    public String saveAuditDetails(List<AuditDetails> auditDetails) {
        // LOGIC TO SAVE AUDIT DETAILS IN DB
        // ...

        System.out.println("Audit details saved successfully.");
        return "SUCCESS";
    }
}

Single Responsibility Principle (SRP)
Here we have our main class. The code snippet describes how to use the classes.
public class SingleResponsibilityMain {
    public static void main(String[] args) {

        // REGISTRATION SERVICE
        UserRegistrationService registrationService = new UserRegistrationService();
        registrationService.register(new UserDetails(0L,
                "Silas Ross",
                "silas_ross@gmail.com",
                "9090801010",
                "silas8090ross"));

        // UPDATE SERVICE
        UserDetailsUpdateService updateService = new UserDetailsUpdateService();
        updateService.updateUserDetails(new UserDetails(101L,
                "Silas Ross",
                "silas_6060_ross@gmail.com",
                "9090806060",
                "silas8090ross"));

        // LOG/AUDIT SERVICE
        AuditService auditService = new AuditService();
        auditService.saveAuditDetails(List.of(new AuditDetails(LocalDateTime.now(),
                "email",
                "silas_ross@gmail.com",
                "silas_6060_ross@gmail.com",
                "user",
                109L),
                new AuditDetails(LocalDateTime.now(),
                        "phone",
                        "9090801010",
                        "9090806060",
                        "user",
                        109L)));

        // OTP SERVICE
        OTPService otpService = new OTPService();
        otpService.sendOTP("9000000010");
        otpService.validateOTP("983417");
    }
}
Here you can see that we tested each class separately.

Using the Single Responsibility Principle, we divided a class's responsibilities or functionalities. This makes our code more modular, flexible, and maintainable. We can also test each class individually. Each class now has only one reason to change, as per the Single Responsibility Principle.

Happy coding!!! 😊
in

Popular posts