• Tuesday, 16-Sep-25 19:07:41 IST
Tech Trending :
* Semantic Search Using Text Embeddings (With ChatGPT + Python) * 🤖How OpenAI + MCP Servers Can Power the Next Generation of AI Agents for Automation * 📚 Book Recommendation System Using OpenAI Embeddings And Nomic Atlas Visualization

Polymorphism, Abstraction, and Runtime Binding in Java with a Payment Example

Polymorphism, Abstraction, and Runtime Binding in Java with a Payment Example

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:

abstract public class Card { private String cardNo; private String userName; public Card(String cardNo, String name){ this.cardNo = cardNo; this.userName = name; } // getters and setters }
public interface PaymentMethods { void pay(int amount); }
  • Card defines common properties like cardNo and userName.

  • PaymentMethods defines 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.

public class CreditCard extends Card implements PaymentMethods { @Override public void pay(int amount) { System.out.println("Making payment by credit card of amount " + amount); } } public class DebitCard extends Card implements PaymentMethods { @Override public void pay(int amount) { System.out.println("Making payment by debit card of amount " + amount); } } public class UPI implements PaymentMethods { @Override public void pay(int amount) { System.out.println("Making payment by UPI of amount " + amount); } }

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:

PaymentService ps = new PaymentService(); ps.addPaymentMethod("Preetam UPI", new UPI("preetam.kumar.icici")); ps.addPaymentMethod("PreetamIciciCreditCard", new CreditCard("987654567", "Preetam kumar")); ps.makePayment("Preetam UPI", 5000);

Inside makePayment:

public void makePayment(String name, int amount){ PaymentMethods pm = paymentMethodsMap.get(name); pm.pay(amount); // Runtime Polymorphism }

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 the UPI class at runtime.

  • If we pass "PreetamIciciCreditCard", it dynamically binds to the CreditCard class.

This is runtime polymorphism in action.


4. UML Class Diagram

Here’s the UML representation of our payment system:

The diagram shows:

  • PaymentMethods as an interface.

  • Card as an abstract class.

  • CreditCard, DebitCard, and UPI implementing PaymentMethods.

  • PaymentService depending on PaymentMethods to achieve runtime polymorphism.


5. Why Is This Powerful?

  • Flexibility → Adding a new payment method (say, NetBanking) requires no changes to existing code, only implementing PaymentMethods.

  • 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():

public class Client { public static void main(String[] args) { PaymentService ps = new PaymentService(); ps.addPaymentMethod("PreetamIciciCreditCard", new CreditCard("987654567", "Preetam kumar")); ps.addPaymentMethod("PreetamIciciDebitCard", new DebitCard("987654567", "Preetam kumar")); ps.addPaymentMethod("Preetam UPI", new UPI("preetam.kumar.icici")); ps.makePayment("Preetam UPI", 5000); } }

Output:

Making payment by UPI of amount 5000

Conclusion

Through this payment system example, we demonstrated:

  • Abstraction → Hiding implementation, showing only necessary details (Card class, PaymentMethods interface).

  • 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.