Friday, December 30, 2011

Factory Method Pattern in Python

Ok, here we go again. This time, I will demonstrate Factory Method Design Pattern  


Let's say we need to design a module to retrieve 4 statistics counters, 
  1. Bytes Transmitted 
  2. Bytes Received
  3. Packet Transmitted
  4. Packets Received
So, we start with creating a base class and 4 inherited classes:


1:  class StatsClass(object):  
2:    
3:    def __init__(self):  
4:      self._counter = 0  
5:      self._name ="Abtract Statistics Class"  
6:        
7:    def Get_Name(self):  
8:      return self._name  
9:      
10:    def Get(self):  
11:      return self._counter;  
12:      
13:    def Reset(self):  
14:      self._counter = 0;  
15:      
16:    def Set(self,counter):  
17:      self._counter = counter;  
18:        
19:    def PollFromHw(self):  
20:      print(self._name, "is poll statistics counter from hardware register")  
21:      
22:    
23:  class StatsBytesTransmitted(StatsClass):  
24:    def __init__(self):  
25:      self._name = "Bytes Transmitted"  
26:      self._counter = 0    
27:    
28:  class StatsBytesReceived(StatsClass):  
29:    def __init__(self):  
30:      self._name = "Bytes Received"  
31:      self._counter = 0  
32:    
33:  class StatsPacketsTransmitted(StatsClass):  
34:    def __init__(self):  
35:      self._name = "Packets Transmitted"  
36:      self._counter = 0  
37:      
38:  class StatsPacketsReceived(StatsClass):  
39:    def __init__(self):  
40:      self._name = "Packets Received"  
41:      self._counter = 0  
42:        


Then, create a Factory Class


1:  # Factory Class  
2:  class AbstractStatsFactory():  
3:    def CreateStats(self,strStats):  
4:      pass  
5:    
6:  class StatsFactory(AbstractStatsFactory):  
7:    def CreateStats(self,strStats):  
8:      if( strStats == "Bytes Received"):  
9:        StatsObj = StatsBytesReceived()  
10:      elif ( strStats == "Bytes Transmitted"):  
11:        StatsObj = StatsBytesTransmitted()  
12:      elif ( strStats == "Packets Transmitted"):  
13:        StatsObj = StatsPacketsTransmitted()    
14:      elif ( strStats == "Packets Received"):  
15:        StatsObj = StatsPacketsReceived()    
16:      else:  
17:        StatsObj = None    
18:    
19:      return StatsObj  




Now, let's put them into use


1:  def Use_StatsObj(StatsObj):   
2:    StatsObj.PollFromHw()  
3:    StatsObj.Set(100)  
4:    print(StatsObj.Get_Name(), "counter is ", StatsObj.Get())    
5:    StatsObj.Reset()  
6:    print(StatsObj.Get_Name(), "counter Reset. Now counter is ", StatsObj.Get())    
7:    
8:    
9:    
10:  strStats = "Bytes Received"  
11:  StatsObj = StatsFactory().CreateStats(strStats)  
12:  Use_StatsObj(StatsObj)  
13:    
14:  strStats = "Bytes Transmitted"  
15:  StatsObj = StatsFactory().CreateStats(strStats)  
16:  Use_StatsObj(StatsObj)  


The output is 
 Bytes Received is poll statistics counter from hardware register  
 Bytes Received counter is 100  
 Bytes Received counter Reset. Now counter is 0  
 Bytes Transmitted is poll statistics counter from hardware register  
 Bytes Transmitted counter is 100  
 Bytes Transmitted counter Reset. Now counter is 0  


No bad so far, though, I am not satisfied.

There is still an if-elif-else statement in the StatsFactory Class. It will be nice if the Factory class knows how many Statistics Objects are there, without explicitly instantiated these concrete objects .


In OO theme, you can use Reflection to do so.  In Python, this is how I implement it


1:  class StatsFactory2(AbstractStatsFactory):  
2:    def CreateStats(self,strStats):  
3:      # Get all subclass  
4:      for childClass in StatsClass.__subclasses__() :  
5:        obj = childClass()  
6:        if( strStats == obj.Get_Name()):  
7:          return obj  
8:    def ListStatsObjNames(self):  
9:      objList = list()  
10:      for childClass in StatsClass.__subclasses__() :  
11:        # print(childClass)  
12:        obj = childClass()  
13:        objList.append(obj.Get_Name())  
14:      return objList   
15:        
16:    


Now, it's time to put the code into test


1:  print("--- now use New Factory --- ")  
2:  strStats = "Bytes Received"  
3:  StatsObj = StatsFactory2().CreateStats(strStats)  
4:  Use_StatsObj(StatsObj)  
5:    
6:  print("Counter can be read: ", StatsFactory2().ListStatsObjNames())  
7:  while(True):  
8:    strStats = input("Which counter do you want to know ?");  
9:    StatsObj = StatsFactory2().CreateStats(strStats)  
10:    if( StatsObj == None ):  
11:      break  
12:    Use_StatsObj(StatsObj)  
13:    


This is the Output


 --- now use New Factory ---   
 Bytes Received is poll statistics counter from hardware register  
 Bytes Received counter is 100  
 Bytes Received counter Reset. Now counter is 0  
 Counter can be read: ['Bytes Transmitted', 'Bytes Received', 'Packets Transmitted', 'Packets Received']  
 Which counter do you want to know ?Bytes Transmitted  
 Bytes Transmitted is poll statistics counter from hardware register  
 Bytes Transmitted counter is 100  
 Bytes Transmitted counter Reset. Now counter is 0  
   


Now, this is beauty part -- let's add two new statistics counters right after the  StatsPacketsReceived 


1:  class StatsFcsError(StatsClass):  
2:    def __init__(self):  
3:      self._name = "Fcs Error"  
4:      self._counter = 0  
5:    
6:  class StatsDiscardPacket(StatsClass):  
7:    def __init__(self):  
8:      self._name = "Discard Packets"  
9:      self._counter = 0    
10:    


Then, run my test code without any change. Voila, the two new classes are automatically recognized and can be used. 


 --- now use New Factory ---   
 Bytes Received is poll statistics counter from hardware register  
 Bytes Received counter is 100  
 Bytes Received counter Reset. Now counter is 0  
 Counter can be read: ['Bytes Transmitted', 'Bytes Received', 'Packets Transmitted', 'Packets Received', 'Fcs Error', 'Discard Packets']  
 Which counter do you want to know ?Discard Packets  
 Discard Packets is poll statistics counter from hardware register  
 Discard Packets counter is 100  
 Discard Packets counter Reset. Now counter is 0  
 Which counter do you want to know ?  




Beautiful, isn't it ?

3 comments:

  1. can you share more on python design pattern? thanks

    ReplyDelete
  2. I read your post about Factory Method Pattern in Python. All the coding which you shared here is simple and also it is easy to understand. Thanks for sharing this.
    Learning Games

    ReplyDelete
  3. I was reading that post and i can't understand the middle section of the post where you say i am not satisfied. Would you like to please elaborate it.
    I am waiting for the update.
    123movies

    ReplyDelete