Chapter[19]: Revisiting Object-Oriented Design: A Comprehensive Payment System in Java

--

Image by freepik

We’ll be revisiting some key concepts of object-oriented design (OOD) while implementing a payment system (which we’ve already built in parts) that uses different methods such as UPI, credit cards, debit cards, and PayPal. By the end of this, you’ll have a solid understanding of how to implement these core principles in a way that’s both practical and easy to follow.

Let’s break this down and explore each concept — Inheritance, Polymorphism, Abstraction, Encapsulation, and Composition(one last time) — and how we can apply them to build a robust payment system.

1. Inheritance: Building on the Basics

In our case, we have a base class called PaymentProcessor that will hold the common logic for processing payments. Then, we extend this base class to create specialized payment types, like UPI, Debit Card, Credit Card, and PayPal.

Here’s how it works: We define a general method for processing payments in the PaymentProcessor class, and then each of the subclasses—UpiPayments, DebitCardPayments, etc.—overrides this method to implement its own specific logic.

2. Polymorphism: Let’s Keep It Flexible

For example, whether we’re processing a UPI payment or a PayPal payment, the method to process the payment remains the same. But the implementation varies depending on the payment type, thanks to polymorphism. So when we call processPayment(), the actual method that gets executed will depend on whether we’re dealing with a UPI, Debit Card, Credit Card, or PayPal instance.

3. Abstraction: Hiding the Complexity

In our design, the PaymentProcessor class is abstract. This means we don't need to know how each payment method is processed when we call it; we just know that it will be processed, and we leave the specifics to the subclasses.

The abstract class defines the method processPayment(), but it’s up to the subclasses (like UpiPayments, DebitCardPayments, etc.) to fill in the details of how the payment actually happens. This keeps our code cleaner and more manageable.

4. Encapsulation: Keeping Data Safe

This principle is especially useful in our UserProfile class, where we store sensitive user data like names and emails. Instead of giving direct access to these fields, we use getter and setter methods.

For instance, instead of directly accessing the user’s name, we call userProfile.getName(). This way, we can control how the data is accessed and updated, adding a layer of security and preventing unexpected changes.

5. Composition: Building with Relationships

While inheritance is great for creating “is-a” relationships (like a DebitCardPayment is a PaymentProcessor), composition helps us create "has-a" relationships. In our system, the PaymentService class "has-a" PaymentProcessor.

This means that instead of extending multiple classes, PaymentService can hold an instance of PaymentProcessor and delegate the responsibility of processing the payment to the appropriate payment class. Composition allows us to easily swap out payment methods without changing the structure of the service itself.

Putting It All Together

Let’s see how they fit into our complete Java solution. We’ll create a class for UserProfile to store the user’s data, a PaymentProcessor class to define the general payment logic, and specialized classes like UpiPayments, DebitCardPayments, CreditCardPayments, and PayPalPayments to handle the specific payment methods.

Finally, the PaymentService class ties everything together, using composition to choose and execute the right payment method based on the user’s choice.

1. User Profile Class (Encapsulation)

This class will store user-related information like name, email, etc., and allow access to these details via getter and setter methods.

public class UserProfile {
private String name;
private String email;
public UserProfile(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
}

2. Payment Processor Class (Abstraction)

This will be an abstract class that defines common methods for different payment methods.

public abstract class PaymentProcessor {
protected UserProfile userProfile;
public PaymentProcessor(UserProfile userProfile) {
this.userProfile = userProfile;
}
public abstract void processPayment(double amount);
}

3. UPI Payments Class (Inheritance & Polymorphism)

This class extends PaymentProcessor and implements the processPayment method.

public class UpiPayments extends PaymentProcessor {
public UpiPayments(UserProfile userProfile) {
super(userProfile);
}
@Override
public void processPayment(double amount) {
System.out.println("Processing UPI payment of " + amount + " for " + userProfile.getName());
}
}

4. Debit Card Payments Class (Inheritance & Polymorphism)

This class extends PaymentProcessor and implements the processPayment method.

public class DebitCardPayments extends PaymentProcessor {
public DebitCardPayments(UserProfile userProfile) {
super(userProfile);
}
@Override
public void processPayment(double amount) {
System.out.println("Processing Debit Card payment of " + amount + " for " + userProfile.getName());
}
}

5. Credit Card Payments Class (Inheritance & Polymorphism)

This class extends PaymentProcessor and implements the processPayment method.

public class CreditCardPayments extends PaymentProcessor {
public CreditCardPayments(UserProfile userProfile) {
super(userProfile);
}

@Override
public void processPayment(double amount) {
System.out.println("Processing Credit Card payment of " + amount + " for " + userProfile.getName());
}
}

6. PayPal Payments Class (Inheritance & Polymorphism)

This class extends PaymentProcessor and implements the processPayment method.

public class PayPalPayments extends PaymentProcessor {
public PayPalPayments(UserProfile userProfile) {
super(userProfile);
}
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of " + amount + " for " + userProfile.getName());
}
}

7. Payment Service Class (Composition)

This class uses composition to manage different payment methods. It allows processing payments through any of the payment classes (UPI, Debit Card, etc.).

public class PaymentService {
private PaymentProcessor paymentProcessor;

public void setPaymentProcessor(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
public void processPayment(double amount) {
paymentProcessor.processPayment(amount);
}
}

8. Main Class (To Run All)

This class will instantiate the user profile and payment classes and process payments using different payment methods.

public class Main {
public static void main(String[] args) {

UserProfile user = new UserProfile("John Doe", "john@example.com");

PaymentService paymentService = new PaymentService();

// Process UPI Payment
PaymentProcessor upiPayment = new UpiPayments(user);
paymentService.setPaymentProcessor(upiPayment);
paymentService.processPayment(1000.50);

// Process Debit Card Payment
PaymentProcessor debitCardPayment = new DebitCardPayments(user);
paymentService.setPaymentProcessor(debitCardPayment);
paymentService.processPayment(2000.75);

// Process Credit Card Payment
PaymentProcessor creditCardPayment = new CreditCardPayments(user);
paymentService.setPaymentProcessor(creditCardPayment);
paymentService.processPayment(1500.30);

// Process PayPal Payment
PaymentProcessor paypalPayment = new PayPalPayments(user);
paymentService.setPaymentProcessor(paypalPayment);
paymentService.processPayment(500.90);
}
}

Key Concepts Used:

  1. Inheritance: Classes like UpiPayments, DebitCardPayments, etc., inherit from the PaymentProcessor class.
  2. Polymorphism: The processPayment method is overridden in each payment method class.
  3. Abstraction: The PaymentProcessor class defines the abstract processPayment method, leaving its implementation to the subclasses.
  4. Encapsulation: The UserProfile class encapsulates the user's details, with getters and setters.
  5. Composition: The PaymentService class uses composition to include a PaymentProcessor object for processing different payment methods.

Key Object-Oriented Concepts Used in the Code:

  1. Encapsulation:
  • The UserProfile class encapsulates user details like name and email. These fields are kept private, and access to them is provided through public getter and setter methods.
  1. Diagram (Encapsulation):
+-----------------------+
| UserProfile |
+-----------------------+
| - name: String | <-- Private field
| - email: String | <-- Private field
+-----------------------+
| + getName(): String | <-- Public getter method
| + getEmail(): String | <-- Public getter method
| + setName(String) | <-- Public setter method
| + setEmail(String) | <-- Public setter method
+-----------------------+
  1. Inheritance:
  • Classes like UpiPayments, DebitCardPayments, CreditCardPayments, and PayPalPayments inherit from the abstract PaymentProcessor class, which allows them to share common behavior and implement specific payment logic.
  1. Diagram (Inheritance):
+-------------------+       +-------------------------+
| PaymentProcessor |<-----| UpiPayments |
+-------------------+ +-------------------------+
| + processPayment() | | + processPayment() | <-- Overridden method
+-------------------+ +-------------------------+
^
|
+-------------------+ +-------------------------+
| DebitCardPayments | | CreditCardPayments |
+-------------------+ +-------------------------+
  1. Polymorphism:
  • The processPayment method is polymorphic, meaning it behaves differently depending on the payment method. Each subclass (e.g., UpiPayments, DebitCardPayments, etc.) provides its own implementation of processPayment.
  1. Diagram (Polymorphism):
+-------------------+      +-------------------------+
| PaymentProcessor |<-----| UpiPayments |
+-------------------+ +-------------------------+
| + processPayment() | | + processPayment() | <-- Different implementation
+-------------------+ +-------------------------+
^
|
+-------------------+ +-------------------------+
| DebitCardPayments | | CreditCardPayments |
+-------------------+ +-------------------------+
  1. Abstraction:
  • The PaymentProcessor class abstracts the processPayment method, allowing subclasses to define their own implementation. The method itself is declared but not implemented in the abstract class.
  1. Diagram (Abstraction):
+------------------------------+
| <<abstract>> PaymentProcessor |
+------------------------------+
| - userProfile: UserProfile | <-- Composition
+------------------------------+
| + processPayment(amount) | <-- Abstract method
+------------------------------+
^
|
+-------------------+ +-------------------------+
| UpiPayments | | DebitCardPayments |
+-------------------+ +-------------------------+
| + processPayment()| | + processPayment() |
+-------------------+ +-------------------------+
  1. Composition:
  • The PaymentService class demonstrates composition by holding a reference to the PaymentProcessor object. This allows the PaymentService to work with different payment types (UPI, Debit Card, etc.) dynamically.
  1. Diagram (Composition):
+---------------------------+
| PaymentService |
+--------------------------------------+
| - paymentProcessor: PaymentProcessor |
+--------------------------------------+
| + setPaymentProcessor() |
| + processPayment() |
+---------------------------+
|
v
+---------------------------+
| PaymentProcessor |
+---------------------------+

Conclusion

By combining Encapsulation, Inheritance, Polymorphism, Abstraction, and Composition, we’ve created a modular and flexible payment system that can be easily extended with new payment methods in the future. This structure provides clear separation of concerns, making the code easier to maintain and scale.

You can find and download the code here.

.

.

.

Happy Coding!

--

--

Automate This. By Mrigank Saxena
Automate This. By Mrigank Saxena

Written by Automate This. By Mrigank Saxena

Join me as I share insights, tips, and experiences from my journey in quality assurance, automation, and coding! https://www.linkedin.com/in/iammriganksaxena/

No responses yet