When building real-world applications, one of the key principles in Object-Oriented Programming (OOP) that makes systems flexible and scalable is polymorphism. Let’s understand how polymorphism, abstraction, and runtime binding work in Java using a payment system example.
1. Abstraction in Action
Abstraction is about hiding implementation details and exposing only the necessary functionality.
In our payment system, we have an abstract class Card and an interface PaymentMethods:
-
Carddefines common properties likecardNoanduserName. -
PaymentMethodsdefines a contract for payment (pay(int amount)), but it doesn’t care how payment is processed.
This ensures different payment types (Credit Card, Debit Card, UPI) can implement their own logic without exposing internal details.
2. Polymorphism: One Interface, Many Implementations
Polymorphism simply means one action, different behaviors.
Here, CreditCard, DebitCard, and UPI all implement the PaymentMethods interface.
All three classes share the same method signature pay(int amount), but the execution differs.
This is the core of polymorphism: one interface, multiple implementations.
3. Runtime Polymorphism and Dynamic Binding
In Java, method overriding enables runtime polymorphism. This means:
-
The decision of which method implementation to call happens at runtime, not at compile time.
-
This is also known as dynamic method dispatch or dynamic binding.
Example from our code:
Inside makePayment:
Here, the type of pm is the interface PaymentMethods, but the actual object could be UPI, CreditCard, or DebitCard.
-
If we pass
"Preetam UPI", it dynamically binds to theUPIclass at runtime. -
If we pass
"PreetamIciciCreditCard", it dynamically binds to theCreditCardclass.
This is runtime polymorphism in action.
4. UML Class Diagram
Here’s the UML representation of our payment system:
The diagram shows:
-
PaymentMethodsas an interface. -
Cardas an abstract class. -
CreditCard,DebitCard, andUPIimplementingPaymentMethods. -
PaymentServicedepending onPaymentMethodsto achieve runtime polymorphism.
5. Why Is This Powerful?
-
Flexibility → Adding a new payment method (say,
NetBanking) requires no changes to existing code, only implementingPaymentMethods. -
Maintainability → Common logic (like managing users and cards) is separated from payment logic.
-
Extensibility → The system can grow with more payment options without breaking old code.
6. Final Example Output
If we run Client.main():
Output:
Conclusion
Through this payment system example, we demonstrated:
-
Abstraction → Hiding implementation, showing only necessary details (
Cardclass,PaymentMethodsinterface). -
Polymorphism → One method (
pay) with multiple behaviors (CreditCard,DebitCard,UPI). -
Runtime Polymorphism / Dynamic Binding → Actual method resolution happens at runtime, based on the object reference.
This design is a practical example of how OOP principles make code reusable, scalable, and easy to maintain.