Early Access: 87 spots left.

Claim
Low Level DesignCompositeComposite: Price a Catalog of Nested Product Bundles

Course content

Composite: Price a Catalog of Nested Product Bundles

Medium·Tagsdesign-patternscompositestructuraltreesrecursion

Problem Statement

The store's catalog has single products and product bundles. A bundle contains catalog items: products or other bundles, nested to any depth. A bundle's price is the sum of its children's prices with the bundle's own percentage discount applied on top; its shipping weight is the plain sum of its children's weights, and discounts never touch weight. ```java interface CatalogItem { double price(); // final price in dollars, after any bundle discounts double weight(); // shipping weight in kg; discounts never apply to weight } ``` `Product implements CatalogItem` is the leaf and is already implemented. Implement `Bundle`, the composite, so that single items and whole trees are priced through the same interface: ```java class Bundle implements CatalogItem { public Bundle(String name, double discountPercent); public void add(CatalogItem item); // Append a child. Products, bundles, and item types that do not // exist yet must all nest freely, so the parameter stays CatalogItem. public double price(); // Sum of children's price() values, times (1 - discountPercent / 100). // An empty bundle costs 0. public double weight(); // Plain sum of children's weight() values. No discount, ever. // An empty bundle weighs 0. } ``` With `Bundle` in place, callers never check types: ```java Bundle artKit = new Bundle("Art Mini-Kit", 20); artKit.add(new Product("Crayons", 4.00, 0.20)); artKit.add(new Product("Sketch Pad", 6.00, 0.30)); Bundle backToSchool = new Bundle("Back to School", 10); backToSchool.add(new Product("Notebook", 5.00, 0.40)); backToSchool.add(artKit); // a bundle inside a bundle double total = backToSchool.price(); // one call walks the whole tree ``` The validator runs three checks: 1. flat_bundle: a bundle of three products prices at the discounted subtotal (9.00 from a 10.00 subtotal at 10% off) and weighs the plain sum (1.00). An empty bundle prices and weighs 0. 2. nested_bundles: bundles nest two levels deep and each level applies its own discount to its own subtree: the inner kit comes to 11.20, the outer kit to 14.58. Weights sum straight through with no discount anywhere. 3. uniform_extension (hidden): `Bundle` is a `CatalogItem` (so bundles nest), does not extend `Product`, exposes `add(CatalogItem)`, and correctly aggregates a brand-new item type the validator defines itself. The stub `Bundle` throws from every method, so it fails every check until implemented. Starter code and validators are available in Java, Python, and C++.

Examples

Example 1
Input
kit = Bundle("School Kit", 10); add Notebook 5.00/0.40kg, Pen Pack 3.00/0.10kg, Marker Set 2.00/0.50kg; kit.price(); kit.weight()
Output
"price 9.00 (10.00 subtotal minus 10%), weight 1.00 (the discount never touches weight)"
Why
The bundle sums its children's prices, applies its own discount to the subtotal, and sums weights with no discount.
Example 2
Input
artKit = Bundle("Art Mini-Kit", 20) holding Crayons 4.00 + Sketch Pad 6.00 + Refill Pack (2.50 + 1.50 at 0% off); backToSchool = Bundle("Back to School", 10) holding Notebook 5.00 + artKit
Output
"artKit.price() = 11.20; backToSchool.price() = 14.58"
Why
Each bundle applies its own discount to its own subtree: (4 + 6 + 4) * 0.8 = 11.20, then (5 + 11.20) * 0.9 = 14.58.
Example 3
Input
order = Bundle("Laptop Order", 0); order.add(Product("Laptop", 10.00, 1.00)); order.add(WarrantyAddon()) // a CatalogItem type defined by the validator
Output
"order.price() = 22.00; order.weight() = 1.00"
Why
The bundle never learns the concrete type. Any class implementing CatalogItem aggregates correctly, which is the point of typing children as the component.

Constraints

  • Bundle implements (derives from) CatalogItem, so bundles can nest inside bundles.
  • Bundle stores its children typed as CatalogItem, never as Product or Bundle.
  • add accepts a CatalogItem: any leaf or composite, including types that do not exist yet.
  • price() returns the sum of the children's price() values times (1 - discountPercent / 100). An empty bundle costs 0.
  • weight() returns the plain sum of the children's weight() values. Discounts never apply to weight. An empty bundle weighs 0.
  • Bundle must not extend Product. Leaf and composite are siblings behind the component interface.

Hints

Stuck? Reveal a nudge toward the right pattern, one step at a time.

Hint 1
Bundle needs three fields: the name, the discount percent, and a list of children typed as the component interface (List<CatalogItem> in Java, a plain list in Python, std::vector<const CatalogItem*> in C++). The constructor stores the first two and starts the list empty.
Hint 2
add is one line: append the item to the children list. Do not branch on the item's concrete type; the whole pattern is that you cannot tell and do not care.
Hint 3
price() is a loop and a multiply: sum child.price() over the children, then return subtotal * (1 - discountPercent / 100.0). The recursion is already there, because a child's price() may itself loop over ITS children.
Hint 4
weight() is the same loop without the multiply. Resist reusing price()'s discounted result; weight gets its own honest recursion over the same children.
Hint 5
The empty bundle needs no special case: an empty loop sums to 0, and 0 times any discount factor is still 0. If you wrote an if for it, delete the if.
Hint 6
The hidden check adds a catalog item class the validator defines itself. Your Bundle passes automatically IF children are typed as CatalogItem everywhere; it fails if you stored Products and Bundles in separate lists or branched on concrete types.