Chapter[17]: Creating an online payment system with abstraction: Part 2
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 callmakePayment
.
Why is this better?
- Simpler to use: Users don’t need to know how payment processing works — they just call a method.
- 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 theprocess
method). It also provides shared functionality likepaymentDetails()
. - Class 2 & Class 3 (
CreditCardPayment
andPayPalPayment
): 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 thePayment
reference to call the appropriate implementation based on what the user selects.
Why Use Abstract Classes?
- Code Reusability: Common logic (like
paymentDetails()
) is defined once in the abstract class and reused by subclasses. - 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
andPayPalPayment
inherit fromPayment
and provide specific implementations for theprocess()
method. - Polymorphism:
PaymentService
works with thePayment
type, so you can pass any subclass ofPayment
to it (e.g.,CreditCardPayment
orPayPalPayment
), 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!