Search code examples
pythonpython-3.xdesign-patternsfactoryfactory-pattern

Programming pattern for combining factory functions


Say you have a bunch of factory functions, each of which does two things:

  1. Modify or add arguments to the initialization of a class
  2. Does something with the class instance afterwards

E.g.

class Dog:
  def __init__(self, **very_many_kwargs):
    pass

def create_police_dog(department, **dog_kwargs):
  dog_kwargs['race'] = 'pitbull_terrier'
  dog = Dog(**dog_kwargs)
  police_academy = PoliceAcademy()
  police_academy.train(dog)
  return dog

def create_scary_dog(**dog_kwargs):
  dog_kwargs['teeth_size'] = 'MAX'
  dog_kwargs['eye_color'] = fetch_angry_eye_colors('https://dogs.com')
  dog = Dog(**dog_kwargs)
  dog.experience_unhappy_childhood()
  return dog

How to combine multiple of such functions in series?


Solution

  • Decorators almost work but since you want your modifications to occur both before and after instantiation, they won't chain properly. Instead define a custom system of generic modifiers that can be chained together at creation time:

    from abc import ABC, abstractmethod                                                                  
                                                                                                         
    class DogModifier(ABC):                                                                              
        @abstractmethod                                                                                  
        def mod_kwargs(self, **kwargs):                                                                  
            pass                                                                                         
                                                                                                         
        @abstractmethod                                                                                  
        def post_init(self, dog):                                                                        
            pass                                                                                         
                                                                                                         
    class PoliceDog(DogModifier):                                                                        
        def __init__(self, department):                                                                  
            self._dept = department                                                                      
                                                                                                         
        def mod_kwargs(self, **kwargs):                                                                 
            kwargs['race'] = 'pitbull_terrier'                                                          
                                                                                                        
        def post_init(self, dog):                                                                       
            PoliceAcademy(self._dept).train(dog)                                                        
                                                                                                            
    class ScaryDog(DogModifier):                                                                        
        def mod_kwargs(self, **kwargs):                                                                 
            kwargs['teeth_size'] = 'MAX'                                                                
            kwargs['eye_color'] = fetch_angry_eye_color('https://dogs.com')                             
                                                                                                        
        def post_init(self, dog):                                                                       
            dog.experience_unhappy_childhood()                                                          
                                                                                                        
    def create_dog(*modifiers, **dog_kwargs):                                                               
        for m in modifiers:                                                                             
            m.mod_kwargs(**dog_kwargs)                                                                      
                                                                                                        
        dog = Dog(**dog_kwargs)                                                                         
                                                                                                        
        for m in modifiers:                                                                             
            m.post_init(dog)                                                                            
                                                                                                        
        return dog                                                                                      
                                                                                                        
    # ...                                                                                               
                                                                                                        
    police_dog = create_dog(PoliceDog('bomb squad'), kw1='a', kw2='b')                                  
    scary_dog = create_dog(ScaryDog(), kw1='x', kw2='y')                                                
    scary_police_dog = create_dog(PoliceDog('bomb squad'), ScaryDog(), kw1='z')
    

    *code shown as example only - bugfixes left as an exercise for the reader