Wednesday, December 28, 2011

Decorator Pattern in Python

I re-read Head-First Design Pattern lately. And decide to implement Decorator Design Pattern in Python.


The following is based on the example from the book

  1. The existing code has base class Beverage, and subclass DarkRoast, Espresso. These code has been fully tested and in production (line 1-21). 
  2. Three months later, your boss want to have more varieties of drink, .e.g Whip Cream, Vanilla. So, you need to write more code to support that.
  3. It's best to use Decorator Design Pattern, as it obeys the OCP(Open-Closed) Principle. (line 24-51)
  4. Mocha, WhipCream, Vanilla are all decorator class.


Source Code

1:  class Beverage:  
2:    """ beverage class """  
3:    def __init__(self):  
4:      self._desc = "Abstract Drink"  
5:      self._cost = 0.0  
6:      
7:    def get_cost(self):  
8:      return self._cost  
9:      
10:    def get_desc(self):  
11:      return self._desc  
12:      
13:  class DarkRoast(Beverage):  
14:    def __init__(self):  
15:      self._cost = 3.5  
16:      self._desc = "Dark Roast ($" + str(self._cost) + ")"  
17:          
18:  class Espresso(Beverage):  
19:    def __init__(self):  
20:      self._cost = 3.0  
21:      self._desc = "Espresso ($" + str(self._cost)+ ")"  
22:    
23:    
24:  # Design Pattern   
25:  # Abstract Decorator  
26:  class Condiments(Beverage):  
27:    def __init__(self):  
28:      self._desc = "Abstract Condiments class"  
29:      self._cost_condiment = 0.0      
30:      
31:  class Mocha(Condiments):  
32:    def __init__(self, beverage):  
33:      self._cost_condiment = 1.0  
34:      self._beverage = beverage;      
35:      self._desc = "Mocha($"+ str(self._cost_condiment)+ ") " + self._beverage.get_desc()   
36:      self._cost = self._cost_condiment + self._beverage.get_cost()   
37:        
38:  class Vanilla(Condiments):  
39:    def __init__(self, beverage):  
40:      self._cost_condiment = 0.6  
41:      self._beverage = beverage;      
42:      self._desc = "Vanilla($"+ str(self._cost_condiment)+ ") " + self._beverage.get_desc()   
43:      self._cost = 0.6 + self._beverage.get_cost()   
44:           
45:    
46:  class WhipCream(Condiments):  
47:    def __init__(self,beverage):  
48:      self._cost_condiment = 0.4  
49:      self._beverage = beverage;      
50:      self._desc = "WhipCream($"+ str(self._cost_condiment)+ ") " + self._beverage.get_desc()   
51:      self._cost = 0.4 + self._beverage.get_cost()   
52:        
53:  ########################################################################################3    
54:    
55:    
56:  b = DarkRoast()  
57:  print(b.get_desc(), "Cost is", b.get_cost())      
58:    
59:  b = Mocha(DarkRoast())  
60:  print(b.get_desc(), "Cost is", b.get_cost())    
61:    
62:  b = Mocha(Espresso())  
63:  print(b.get_desc(), "Cost is", b.get_cost())    
64:    
65:  b = Vanilla(DarkRoast())  
66:  print(b.get_desc(), "Cost is", b.get_cost())   
67:    
68:  b = Vanilla(Mocha(DarkRoast()))  
69:  print(b.get_desc(), "Cost is", b.get_cost())   
70:    
71:  b = WhipCream(Mocha(DarkRoast()))  
72:  print(b.get_desc(), "Cost is", b.get_cost())   
73:    
74:    
75:  b = Vanilla(WhipCream(Mocha(DarkRoast())))  
76:  print(b.get_desc(), "Cost is", b.get_cost())   
77:    


Output

 Dark Roast ($3.5) Cost is 3.5  
 Mocha($1.0) Dark Roast ($3.5) Cost is 4.5  
 Mocha($1.0) Espresso ($3.0) Cost is 4.0  
 Vanilla($0.6) Dark Roast ($3.5) Cost is 4.1  
 Vanilla($0.6) Mocha($1.0) Dark Roast ($3.5) Cost is 5.1  
 WhipCream($0.4) Mocha($1.0) Dark Roast ($3.5) Cost is 4.9  
 Vanilla($0.6) WhipCream($0.4) Mocha($1.0) Dark Roast ($3.5) Cost is 5.5  


Why not using Python built-in Decorator ?

This is good question. My reasons are

  1. You have to modify the existing code. In the source code above, you have to add @decorator in DarkRoast or Espresso class. With Decorator Design Pattern, you don't have to modify existing code. 
  2. You cannot undo the @decorator during run-time. Once it's there, it's there forever. If you use Decorator Design Pattern, you still can enjoy the original espresso. :)




1 comment:

  1. Thanks for the example. I've found it very interesting and useful for me.

    ReplyDelete