Chapter[16]: Passing Class Objects as Parameters in Java: Payment System Example
In this article, we’ll explore how to pass objects as parameters in Java using a payment system example. By the end, you’ll understand how parent class references work, how child class objects are passed, and how dynamic method dispatch comes into play.
Building a Payment System: The Basics
Let’s say you’re creating an online payment system that supports multiple payment methods like Credit Cards and PayPal. Each payment method is represented as a class that extends a common Payment
parent class. Here’s an example:
abstract class Payment {
abstract void processPayment();
}
class CreditCardPayment extends Payment {
@Override
void processPayment() {
System.out.println("Processing credit card payment...");
}
}
class PayPalPayment extends Payment {
@Override
void processPayment() {
System.out.println("Processing PayPal payment...");
}
}
Here’s what’s happening:
- Parent Class (
Payment
): Defines the structure (theprocessPayment()
abstract method) that all child classes must implement. - Child Classes (
CreditCardPayment
andPayPalPayment
): Provide specific implementations for theprocessPayment()
method.
Breaking Down the Concept
1: What is a Subtype?
A subtype is a class that inherits from or implements another class or interface. For example:
abstract class Payment { ... } // Parent class
class CreditCardPayment extends Payment { ... } // Subtype
class PayPalPayment extends Payment { ... } // Subtype
Here, CreditCardPayment
and PayPalPayment
are subtypes of the Payment
class because they inherit or extend from it.
2: What Does Payment payment1
Mean in the below Example?
Payment payment1
declares a variable of type Payment
. Since Payment
is the parent class, this variable can reference objects of the Payment
type or any of its subtypes.
3: Holding Objects of Subtypes
Even though payment1
is declared as Payment
, it can hold instances of any class that is a subtype of Payment
. For example:
Payment payment1 = new CreditCardPayment(); // `payment1` holds a `CreditCardPayment` object
Payment payment2 = new PayPalPayment(); // `payment2` holds a `PayPalPayment` object
4: Child is-a Parent: In Oops we refer the relation between child and parent as Child is-a Parent, and the above objet creation is allowed because a CreditCardPayment
is-a Payment
(via inheritance), and a PayPalPayment
is-a Payment
.
Why Is This Useful?
Using parent references allows you to write flexible code. For instance, you can write methods that operate on Payment
objects without needing to know the exact subtype. Once a Payment
reference is created, you can use it to pass any number of child objects.
Here’s an example:
void processTransaction(Payment payment) {
payment.processPayment();
}
At runtime, the method called on payment
will depend on the actual object (subtype) it holds parent1, or parent2. This is called dynamic method dispatch. For example:
Payment payment1 = new CreditCardPayment();
processTransaction(payment1); // Calls `processPayment()` in `CreditCardPayment`
How Parent Class References Work
When you write this code:
Payment payment1 = new CreditCardPayment();
Payment payment2 = new PayPalPayment();
Here’s what’s happening:
- Declaring a Parent Class Reference:
Payment payment1
creates a variable of typePayment
. This variable can hold any object that is a subtype ofPayment
. - Creating a Child Class Object:
new CreditCardPayment()
creates an instance of theCreditCardPayment
class. - Assigning the Child Object to the Parent Reference: Java allows this because of inheritance. A
CreditCardPayment
is-aPayment
, so it can be stored in aPayment
variable.
This concept is a cornerstone of polymorphism in Java, where parent class references can refer to child class objects.
Passing Objects as Parameters
Let’s pass these objects to a method:
public class Main {
public static void main(String[] args) {
Payment payment1 = new CreditCardPayment();
Payment payment2 = new PayPalPayment();
processTransaction(payment1); // Pass `CreditCardPayment` object
processTransaction(payment2); // Pass `PayPalPayment` object
}
static void processTransaction(Payment payment) {
payment.processPayment(); // Dynamic method dispatch
}
}
What Happens Here?
1: Creating Objects:
payment1
is aCreditCardPayment
object stored in aPayment
reference.payment2
is aPayPalPayment
object stored in aPayment
reference.
2: Passing Objects to processTransaction
:
- When
processTransaction(payment1)
is called, theCreditCardPayment
object is passed as the argument. - When
processTransaction(payment2)
is called, thePayPalPayment
object is passed as the argument.
3: Dynamic Method Dispatch: Inside processTransaction
, the processPayment()
method is called on the Payment
object. At runtime, Java determines which implementation to execute based on the actual object type.
Processing credit card payment...
Processing PayPal payment...
Skipping the Variable: Passing Objects Directly
Instead of storing the object in a variable, you can directly pass it to the method:
processTransaction(new CreditCardPayment());
processTransaction(new PayPalPayment());
How This Works:
- Object Creation:
new CreditCardPayment()
creates a new instance of theCreditCardPayment
class. - Direct Passing: This object is directly passed to the
processTransaction
method, skipping the intermediate variable declaration.
// With Variable
Payment payment1 = new CreditCardPayment();
validatePayment(payment1);
processTransaction(payment1);
// Passing Directly
processTransaction(new CreditCardPayment());
Key Takeaways
- Parent References Hold Child Objects: A
Payment
variable can store aCreditCardPayment
orPayPalPayment
object because of inheritance. - Dynamic Method Dispatch: Java determines at runtime which
processPayment
implementation to call based on the actual object type. - Flexibility: Using parent references makes your code more flexible and scalable. You can add new payment types (e.g.,
UPIPayment
) without modifying existing methods. - Direct Passing: Passing objects directly is concise for one-time use cases but might reduce readability if overused.
By mastering these concepts, you can build scalable, reusable, and flexible Java applications — like a robust payment system! 🚀
Mastering The Composition
How to effectively use IS-A (inheritance) and HAS-A (composition) relationships in Java, along with abstraction. By the end, you’ll gain clarity through practical examples, progressing from the basics to more advanced implementations(in the next chapter).
Basics of IS-A and HAS-A Relationships
Before diving into abstraction, let’s clarify the two foundational concepts:
1: IS-A Relationship (Inheritance):
- Depicts that one class is a type of another class.
- Achieved through
extends
in Java.
2: HAS-A Relationship (Composition):
- Shows that one class contains another class as part of its structure.
- Achieved by including a class as a member variable.
Here’s a simple example with Car
and Engine
:
==============================================================================
// IS-A Relationship: Car is a type of Vehicle
class Vehicle {
void start() {
System.out.println("Vehicle is starting...");
}
}
class Car extends Vehicle {
void drive() {
System.out.println("Car is driving...");
}
}
==============================================================================
// HAS-A Relationship: Car has an Engine
class Engine {
void run() {
System.out.println("Engine is running...");
}
}
class CarWithEngine {
private Engine engine; // Car HAS-A Engine
public CarWithEngine(Engine engine) {
this.engine = engine;
}
void startCar() {
System.out.println("Car is starting...");
engine.run(); // Delegating the task to the Engine
}
}
==============================================================================
// Main Class to Test Relationships
public class CarEngineExample {
public static void main(String[] args) {
// Demonstrating IS-A Relationship
Car myCar = new Car();
myCar.start(); // Inherited from Vehicle
myCar.drive(); // Specific to Car
System.out.println(); // Separator
// Demonstrating HAS-A Relationship
Engine carEngine = new Engine();
CarWithEngine carWithEngine = new CarWithEngine(carEngine);
carWithEngine.startCar(); // Using the Engine's functionality
}
}
==============================================================================
Output:
Vehicle is starting...
Car is driving...
Car is starting...
Engine is running...
By combining these relationships with abstraction, you can build flexible, scalable, and maintainable Java applications. Let me know if you’d like to explore further!
You can find and download the codes from here
.
.
.
Happy Coding!