Hoinzey

Javascript. Kotlin. Android. Java

Design Patterns: Decorator

The example used in these notes is based off that in the "Head First Design Patterns" book by Eric Freeman and Elisabeth Robson.


The decorator pattern is an example of composition over inheritance. It allows us to extend the functionality of an object dynamically at runtime.


The easiest example to demonstrate this pattern is write code for a cashier machine assuming you can add or remove items from the concrete objects available.

Hoinzey's Burgers

I'm not sure if you have ever worked in a burger shop on a till, but you don't have 100 different options in front of you for a burger of each variation; beef burger with cheese; beef burger with onion; beef burger with cheese and onion etc etc.


Instead you'll see an option for the beef burger and then options to add extra cheese or onion. This is the crux of the pattern. You aren't creating 100 concrete objects as above (class explosion) instead you are adding functionality (in this case the cost of the extras) to the beef burger object.


Our Abstract Burger
Our condiment wrapper
                    
    public abstract class ABurger {

        public abstract double getCost();
    
        public abstract String getDescription();
    }
                                        
                            
    public class BurgerCondiment extends ABurger {

        private final ABurger burger;
    
        public BurgerCondiment(ABurger burger) {
            this.burger = burger;
        }
    
        @Override
        public double getCost() {
            return burger.getCost();
        }
    
        @Override
        public String getDescription() {
            return burger.getDescription();
        }
    }
                                        

The abstract burger

Each of our Burger types will extend from this type, providing their own description and costs.

The condiment wrapper

This is what all of our extras will extend. This wrapper needs to extend our Burger class to enable type matching. It contains a reference to a concrete ABurger object. The concrete examples of our extras will add their cost to that of the Burger reference they hold.


Beef burger
Extra cheese
                    
    public class BeefBurger extends ABurger {

        @Override
        public double getCost() {
            return 4.30;
        }
    
        @Override
        public String getDescription() {
            return "Beef burger";
        }
    }
                                        
                    
    public class ExtraCheese extends BurgerCondiment {

        public ExtraCheese(ABurger burger) {
            super(burger);
        }
    
        @Override
        public double getCost() {
            return super.getCost() + 0.70;
        }
    
        @Override
        public String getDescription() {
            return super.getDescription() + " with extra cheese";
        }
    }
                                        

A Beef Burger

This is a concrete implementation of our Burger class.

Extra Cheese

This is a concrete implementation of our wrapper class. You can see in the getCost() method we take the cost from our burger reference and add the cost of the cheese.


Putting it together

                    
    fun main(args: Array) {
        var beefBurger: ABurger = BeefBurger()
        println("${beefBurger.cost}") // 4.4
        beefBurger = ExtraCheese(beefBurger)
        println("${beefBurger.cost}") //5.1
        beefBurger = ExtraBacon(beefBurger)
        println("${beefBurger.cost}") //6.6
    }
                

Pitfalls

Discount on concrete class

Lets say for example you want to add a discount on BeefBurgers, but all other burgers are not discounted. Once the burger is decorated we no longer know it was originally a beef burger.

Decorators don't know each other

The decorators don't know what other decorators are present. So you could get a description of "Cheese, bacon, cheese" instead of for example "Double cheese and bacon". You could instead provide a list of decorators and have the getDescription method format these for you.