DEV Community

NOOB
NOOB

Posted on

LLD-7:Coffee Shop System

Coffee Shop System - Decorator Pattern Implementation

This is an implementation of the Decorator design pattern for a coffee shop billing system where customers can dynamically add various toppings to their base coffee order.

Problem Statement

Building billing software for a coffee shop (like Starbucks) where customers can customize their coffee with various add-ons. The challenge is to handle dynamic combinations of add-ons without creating separate classes for every possible combination (Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, etc.).

Key Challenge: A customer can order:

  • Just Coffee
  • Coffee + Milk
  • Coffee + Milk + Sugar
  • Coffee + Milk + Milk (Double Milk!)
  • Any combination of add-ons in any order

Class Diagram

          +----------------+
          |     Coffee     | <-------------------------+
          |   (Interface)  |                           |
          +----------------+                           |
          | + getCost()    |                           |
          | + getDesc()    |                           |
          +-------+--------+                           |
                  ^                                    |
                  | (Implements)                       |
                  |                                    | (Wraps / Has-A)
       +----------+-----------+             +----------+----------+
       |                      |             |                     |
 +-----+--------+    +--------+-------------+---+      +----------+----------+
 | SimpleCoffee |    |      CoffeeDecorator     |<>----|   coffee field      |
 +--------------+    |         (Abstract)       |      +---------------------+
 | Returns 10.0 |    +--------------------------+
 +--------------+                 ^
                                  | (Extends)
                                  |
                   +--------------+--------------+
                   |                             |
          +--------+--------+           +--------+--------+
          |  MilkDecorator  |           |  SugarDecorator |
          +-----------------+           +-----------------+
          | + getCost()     |           | + getCost()     |
          |   return 2.0    |           |   return 5.0    |
          |   + super.cost  |           |   + super.cost  |
          +-----------------+           +-----------------+
Enter fullscreen mode Exit fullscreen mode

Implementation

package decorator.coffeeshopsystem;

public class CoffeeShopSystem {

    /**
     * Component Interface.
     * Defines the blueprint for both the base object and the decorators.
     */
    interface Coffee {
        String getDescription();
        double getCost();
    }

    /**
     * Concrete Component.
     * The base object (plain coffee) that we will decorate.
     */
    static class SimpleCoffee implements Coffee {
        @Override
        public String getDescription() {
            return "Simple Coffee";
        }

        @Override
        public double getCost() {
            return 10.0;
        }
    }

    /**
     * Abstract Decorator.
     * Implements the interface AND holds a reference to a Component (HAS-A relationship).
     */
    static abstract class CoffeeDecorator implements Coffee {
        protected Coffee coffee; // The object being wrapped

        public CoffeeDecorator(Coffee coffee) {
            this.coffee = coffee;
        }

        @Override
        public String getDescription() {
            return coffee.getDescription(); // Delegate to the wrapped object
        }

        @Override
        public double getCost() {
            return coffee.getCost(); // Delegate to the wrapped object
        }
    }

    /**
     * Concrete Decorator (Milk).
     * Adds functionality (cost and description) to the wrapped object.
     */
    static class MilkDecorator extends CoffeeDecorator {
        public MilkDecorator(Coffee coffee) {
            super(coffee);
        }

        @Override
        public String getDescription() {
            return super.getDescription() + ", Milk";
        }

        @Override
        public double getCost() {
            return super.getCost() + 2.0; // Add cost of Milk
        }
    }

    /**
     * Concrete Decorator (Sugar).
     * Adds functionality (cost and description) to the wrapped object.
     */
    static class SugarDecorator extends CoffeeDecorator {
        public SugarDecorator(Coffee coffee) {
            super(coffee);
        }

        @Override
        public String getDescription() {
            return super.getDescription() + ", Sugar";
        }

        @Override
        public double getCost() {
            return super.getCost() + 5.0; // Add cost of Sugar
        }
    }

    public static void main(String[] args) {
        System.out.println("---- Coffee Shop Order ----");

        // 1. Order Base Coffee
        Coffee myCoffee = new SimpleCoffee();
        System.out.println(myCoffee.getDescription() + " : $" + myCoffee.getCost());

        // 2. Decorate with Milk
        myCoffee = new MilkDecorator(myCoffee);
        System.out.println(myCoffee.getDescription() + " : $" + myCoffee.getCost());

        // 3. Decorate with Sugar
        myCoffee = new SugarDecorator(myCoffee);
        System.out.println(myCoffee.getDescription() + " : $" + myCoffee.getCost());
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Features

  • Decorator Pattern: Dynamically adds responsibilities to objects without modifying their classes
  • Flexible Combinations: Supports any combination of add-ons in any order
  • Open/Closed Principle: Open for extension (new decorators), closed for modification
  • Single Responsibility: Each decorator has one specific responsibility
  • Composition over Inheritance: Uses wrapping instead of subclassing
  • Stackable Decorators: Can apply multiple decorators to the same object

How It Works

  1. Component Interface (Coffee): Defines the common interface for both base objects and decorators
  2. Concrete Component (SimpleCoffee): The basic coffee without any add-ons
  3. Abstract Decorator (CoffeeDecorator): Implements the Coffee interface and holds a reference to a Coffee object
  4. Concrete Decorators (MilkDecorator, SugarDecorator): Add specific functionality by wrapping the coffee object
  5. Wrapping Process: Each decorator wraps the previous object, creating a chain of decorators

Sample Output

---- Coffee Shop Order ----
Simple Coffee : $10.0
Simple Coffee, Milk : $12.0
Simple Coffee, Milk, Sugar : $17.0
Enter fullscreen mode Exit fullscreen mode

Visualization of Wrapping

Order: Coffee + Milk + Sugar

SugarDecorator
    └── wraps MilkDecorator
            └── wraps SimpleCoffee

When getCost() is called:
1. SugarDecorator.getCost() = super.getCost() + 5.0
2. MilkDecorator.getCost() = super.getCost() + 2.0
3. SimpleCoffee.getCost() = 10.0
4. Result: 10.0 + 2.0 + 5.0 = 17.0
Enter fullscreen mode Exit fullscreen mode

Real-World Applications

  • Coffee shop ordering systems (Starbucks, Dunkin')
  • Pizza topping customization
  • Car configuration with optional features
  • Insurance policy add-ons
  • Software licensing with feature modules
  • Stream/IO operations in Java (BufferedReader wrapping FileReader)
  • UI component decoration (borders, scrollbars)

Advantages Over Inheritance

Without Decorator (Using Inheritance):

  • CoffeeWithMilk
  • CoffeeWithSugar
  • CoffeeWithMilkAndSugar
  • CoffeeWithDoubleMilk
  • CoffeeWithDoubleMilkAndSugar
  • ... (Class explosion!)

With Decorator:

  • SimpleCoffee
  • MilkDecorator
  • SugarDecorator
  • Can create any combination dynamically!

Adding New Decorators

Adding a new add-on is simple:

static class WhippedCreamDecorator extends CoffeeDecorator {
    public WhippedCreamDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Whipped Cream";
    }

    @Override
    public double getCost() {
        return super.getCost() + 3.0;
    }
}
Enter fullscreen mode Exit fullscreen mode

No need to modify existing code - just add the new decorator class!

Top comments (0)