The refactored code uses the Composite Pattern. It builds objects into a tree and lets you use the whole tree as if it were one object. The new CartItem interface gives Uniformity: the client does not care whether it holds a single product or a bundle.
1. Key Improvements
Uniformity and Polymorphism
The Benefit: Both Product (Leaf) and ProductBundle (Composite) implement the same CartItem interface.
The Impact: The client has no instanceof checks or casts. It calls getPrice() or display() on a CartItem.
Recursive Hierarchy (Nesting)
The Benefit: ProductBundle now holds a list of CartItem, not just Product.
The Impact: A bundle can now contain other bundles, so trees can go deep (e.g., a "Mega Combo" containing an "iPhone Bundle" and a "Case Bundle").
Type Safety
The Benefit: The cart is now a List<CartItem> instead of a List<Object>.
The Impact: The compiler now checks the types, so a ClassCastException cannot happen here.
Open/Closed Principle (OCP)
The Benefit: You can add new item types without changing old code.
The Impact: To add a new type such as DigitalService or GiftCard, implement CartItem. Main and ProductBundle stay unchanged.
2. Structural Breakdown
Component
Responsibility
Component (CartItem)
The interface with the operations that both simple and complex elements share.
Leaf (Product)
A basic element with no sub-elements.
Composite (ProductBundle)
An element that holds sub-elements (leaves or other composites). It passes the work to its children.
Client (Main)
Uses all elements only through CartItem.
3. Comparison of "Before" vs "After"
Feature
Before (Manual Type Checking)
After (Composite Pattern)
Client Logic
Complex; uses instanceof and casting.
Simple; calls interface methods polymorphically.
Nesting Capabilities
Flat; bundles only hold products.
Recursive; bundles can hold bundles.
Type Safety
Low; uses List<Object>.
High; uses List<CartItem>.
Abstraction
Leaky; client knows internal types.
Hidden; client only knows the interface.
4. Summary of the "Tree"
The Composite Pattern turns your data into a Tree Structure. Ask the "root" (the Cart) for the total price, and it asks its children. A "Leaf" returns its own price. A "Composite" asks its own children. This recursive delegation (a method calling the same method on its children) goes on until the whole tree is done.
1package DesignPatterns.StructuralPatterns.CompositeDesignPattern.After;23import java.util.*;45/**6 * 1. THE COMPONENT INTERFACE7 * This is the heart of the pattern. It defines the common operations for8 * both simple items (Leaf) and groups of items (Composite).9 */10interface CartItem {11 double getPrice();12 void display(String indent);13}1415/**16 * 2. THE LEAF17 * Represents individual products. It implements the interface methods18 * directly without knowing about any other items.19 */20class Product implements CartItem {2122 private String name;23 private double price;2425 public Product(String name, double price) {26 this.name = name;27 this.price = price;28 }2930 @Override31 public double getPrice() {32 return price;33 }3435 @Override36 public void display(String indent) {37 System.out.println(indent + "Product: " + name + " - ₹" + price);38 }39}4041/**42 * 3. THE COMPOSITE43 * Represents a bundle or collection.44 * Key Concept: It contains a list of 'CartItem' (the interface), not 'Product'.45 * This allows it to hold both Products AND other ProductBundles.46 */47class ProductBundle implements CartItem {4849 private String bundleName;50 // Uniformity: Holds any CartItem (Leaf or another Composite)51 private List<CartItem> items = new ArrayList<>();5253 public ProductBundle(String bundleName) {54 this.bundleName = bundleName;55 }5657 public void addItem(CartItem item) {58 items.add(item);59 }6061 @Override62 public double getPrice() {63 double total = 0;64 // Delegation: The bundle doesn't calculate its own price;65 // it asks each child item for its price and sums them up.66 for (CartItem item : items) {67 total += item.getPrice();68 }69 return total;70 }7172 @Override73 public void display(String indent) {74 System.out.println(indent + "Bundle: " + bundleName);75 for (CartItem item : items) {76 // Recursive call: Works regardless of how deep the tree goes77 item.display(indent + " ");78 }79 }80}8182/**83 * 4. THE CLIENT84 * Uses polymorphism to interact with items.85 */86class Main {8788 public static void main(String[] args) {89 // Treat everything as a 'CartItem'90 CartItem book = new Product("Atomic Habits", 499);9192 // Create a bundle and add items93 ProductBundle iphoneCombo = new ProductBundle("iPhone Essentials Combo");94 iphoneCombo.addItem(new Product("iPhone 15", 79999));95 iphoneCombo.addItem(new Product("AirPods", 15999));9697 // TYPE SAFETY: No more List<Object>.98 // We use the common interface type.99 List<CartItem> cart = new ArrayList<>();100 cart.add(book);101 cart.add(iphoneCombo);102103 System.out.println("Your Amazon Cart:");104 double total = 0;105106 // NO INSTANCEOF: Polymorphism handles the call correctly.107 // Whether it's a Product or a Bundle, we just call display() and getPrice().108 for (CartItem item : cart) {109 item.display(" ");110 total += item.getPrice();111 }112113 System.out.println("\nTotal: ₹" + total);114 }115}116