Design Pattern #3: Decorator Pattern

These are my notes for the book Head first design patterns.

Overview

  • Give objects new responsibilities without making any code changes to the underlying class, even at runtime.

Example problem

  • Starbuzz's cost() function for beverage + condiments combinations

  • Solution:

/**
 * Created by jsun on 11/7/2015 AD.
 */
abstract class Beverage { // abstract component
  def cost:Double
}

class DarkRoast extends Beverage{ // concrete component
  def cost = 2.00
}

// we pass in the decorated object in constructor; decorator object is like a wrapper
// decorator object mirror the types of decorated object (both extends Beverage here)
// a class for condiments only --> not really necessary? 
abstract class CondimentDecorator(beverage: Beverage) extends Beverage // abstract decorator

class Mocha(beverage: Beverage) extends CondimentDecorator(beverage){
  def cost = 0.5 + beverage.cost // "composing" behaviors
}

class Whip(beverage: Beverage) extends CondimentDecorator(beverage){
  def cost = 0.2 + beverage.cost
}

object Main{
  def main(args:Array[String]):Unit = {
    val drink = new Whip(new Mocha(new DarkRoast))
    println(drink.cost) // 2 + 0.5 + 0.2 = 2.7
  }
}

Decorator Pattern

  • The Decorator Pattern attached additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

  • Decorators have the same supertype as decorated.

  • Each decorator HAS-A a component, i.e the decorator has an instance variable that holds a reference to a component

  • Decorators are typically created by using other patterns like Factory and Builder.

  • Downside: often result in a large number of small classes that can be overwhelming; can increase component instantiation complexity (can be mitigated by Factory/Builder pattern)

  • Basically "wrappers"

Design Principle

  • Classes should be open for extension, but closed for modification. Concentrate on areas that are most likely to change in the future.

Example in real life: java.io package

  • all decorators: eg. LineNumberInputStream -> BufferedInputStream -> FileInputStream
  • abstract component: InpustStream
  • concrete component: FileInputStream, StringBufferInputStream, etc
  • abstract decorator: FilterInputStream
  • concrete decorator: BufferedInputStream, LineNumberInputStream, etc.

Scala

  • Use implicits to add behavior without changing original class code (not runtime though)
object ExtendedModels{
  implicit class ExtendedBeverage(beverage: Beverage){
    def costWithMilk = 0.2 + beverage.cost // Beverage now has a costWithMilk() method, not the best example
  }
}

Comments

Comments powered by Disqus