Chapter[17]: Creating an online payment system with abstraction: Part 2

Automate This. By Mrigank Saxena
4 min readJan 27, 2025

--

Designed By Freepik

Alright, let’s put what we learned into action! How about we build an Online Payment System to truly get the hang of abstraction? This will help us see how everything clicks together in a real-world scenario.

Code Example: Online Payment System

Here’s how you can build an abstraction for a payment system where the internal logic is hidden, and the user only interacts with a simple service.

// Class 1: Handles the hidden payment processing logic
class PaymentProcessor {
void process(double amount) {
System.out.println("Connecting to the bank...");
System.out.println("Encrypting payment details...");
System.out.println("Processing payment of $" + amount);
System.out.println("Payment successful!");
}
}

// Class 2: Exposes a simple interface for users to make payments
class PaymentService {
private PaymentProcessor processor = new PaymentProcessor(); // Hiding the logic
void makePayment(double amount) {
System.out.println("Initiating payment...");
processor.process(amount); // Hides the internal details
}
}
public class Main {
public static void main(String[] args) {
// User interacts with the PaymentService, not the backend logic
PaymentService service = new PaymentService();
service.makePayment(100.50); // Simple and clean
}
}

What’s Happening?

  • Class 1 (PaymentProcessor): Contains all the complicated details like connecting to the bank, encrypting data, and processing payments.
  • Class 2 (PaymentService): Acts as a bridge between the user and the backend logic. The user doesn’t interact directly with the processor; they only call makePayment.

Why is this better?

  1. Simpler to use: Users don’t need to know how payment processing works — they just call a method.
  2. Safer: Sensitive logic is hidden and can’t be accessed directly.

The abstract Keyword

Now, let’s step up our game. The abstract keyword allows you to define a blueprint for a class. Think of it as saying, “Here’s what every payment method must do, but I won’t tell you how to do it. You figure that out in your own way.”

With abstraction, we define the common behavior of all payment methods while letting each one have its own specific implementation.

Code Example: Abstract Class for Payment Methods

Here’s how you might use the abstract keyword to create a blueprint for different payment methods (like Credit Card and PayPal).

// Class 1: Abstract blueprint for payments
abstract class Payment {
// Abstract method: Subclasses must implement this
abstract void process(double amount);

// Regular method: Common functionality for all payment types
void paymentDetails() {
System.out.println("Processing payment securely...");
}
}


// Class 2: Handles credit card payments
class CreditCardPayment extends Payment {
@Override
void process(double amount) {
paymentDetails(); // Reuse common logic
System.out.println("Processing credit card payment of $" + amount);
}
}


// Class 3: Handles PayPal payments
class PayPalPayment extends Payment {
@Override
void process(double amount) {
paymentDetails(); // Reuse common logic
System.out.println("Processing PayPal payment of $" + amount);
}
}


// Class 4: Exposes a service for users
class PaymentService {
private Payment payment; // Holds a reference to the abstract Payment class
PaymentService(Payment payment) {
this.payment = payment; // User specifies the payment type
}
void makePayment(double amount) {
payment.process(amount); // Delegates processing to the specific payment type
}
}


// Class 5: Main Class
public class Main {
public static void main(String[] args) {
PaymentService service;
// Using Credit Card payment
service = new PaymentService(new CreditCardPayment());
service.makePayment(150.75);
// Switching to PayPal payment
service = new PaymentService(new PayPalPayment());
service.makePayment(200.50);
}
}

What’s Happening?

  • Class 1 (Payment): This is the abstract class. It defines the blueprint that every payment type must follow (via the process method). It also provides shared functionality like paymentDetails().
  • Class 2 & Class 3 (CreditCardPayment and PayPalPayment): These subclasses implement the specific logic for processing payments while reusing the shared functionality.
  • Class 4 (PaymentService): This class interacts with the user. It uses the Payment reference to call the appropriate implementation based on what the user selects.

Why Use Abstract Classes?

  1. Code Reusability: Common logic (like paymentDetails()) is defined once in the abstract class and reused by subclasses.
  2. Flexibility: You can add new payment methods (like Google Pay) by creating another subclass without changing existing code.

Limitations of Abstract Classes

  • Single Inheritance Only: A class can extend only one abstract class. If you need to inherit multiple behaviors, you need interfaces (we’ll cover this in Part 3).
  • Partial Abstraction: Abstract classes can have both abstract and non-abstract methods, so they might not fully enforce abstraction.

Summary

  • Abstract class (Payment): Provides a common structure for all payment types.
  • Inheritance: CreditCardPayment and PayPalPayment inherit from Payment and provide specific implementations for the process() method.
  • Polymorphism: PaymentService works with the Payment type, so you can pass any subclass of Payment to it (e.g., CreditCardPayment or PayPalPayment), making it flexible and reusable.

This should give you a clear understanding of how abstraction works with real-world examples! Let me know if you have questions or need further simplifications. 😊

You can find and download the codes from 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