Builder
This pattern is useful when creating more complicated objects that would take a lot of initializer arguments. Instead, you call a number of methods for creating the object step by step. The idea is that the builder class should expose methods to add properties to the object its creating, with a final method build
to return the object. These methods can be made fluent by returning self
from the function, allowing you to chain these calls together. Builders can also be broken down into Facets, which are inherited builders exposed through the base builder class as properties to handle related elements of the object you are creating. This technically violates the open closed principle because you need to add properties to the base builder, however the alternative is creating a chain of inheritance where you instantiate the most specific builder that has access to all the inherited methods which is somewhat clunky.
class Person:
def __init__(self):
print('Creating an instance of Person')
# address
self.street_address = None
self.postcode = None
self.city = None
# employment info
self.company_name = None
self.position = None
self.annual_income = None
def __str__(self) -> str:
return f'Address: {self.street_address}, {self.postcode}, {self.city}\n' +\
f'Employed at {self.company_name} as a {self.postcode} earning {self.annual_income}'
@staticmethod
def new():
return PersonBuilder()
class PersonBuilder: # facade
# Good practice to take person as an argument
def __init__(self, person=Person()):
self.person = person
@property
def lives(self):
return PersonAddressBuilder(self.person)
@property
def works(self):
return PersonJobBuilder(self.person)
def build(self):
return self.person
class PersonJobBuilder(PersonBuilder):
def __init__(self, person):
super().__init__(person)
def at(self, company_name):
self.person.company_name = company_name
return self
def as_a(self, position):
self.person.position = position
return self
def earning(self, annual_income):
self.person.annual_income = annual_income
return self
class PersonAddressBuilder(PersonBuilder):
def __init__(self, person):
super().__init__(person)
def at(self, street_address):
self.person.street_address = street_address
return self
def with_postcode(self, postcode):
self.person.postcode = postcode
return self
def in_city(self, city):
self.person.city = city
return self
if __name__ == '__main__':
pb = PersonBuilder()
p = pb\
.lives\
.at('123 London Road')\
.in_city('London')\
.with_postcode('SW12BC')\
.works\
.at('Fabrikam')\
.as_a('Engineer')\
.earning(123000)\
.build()
print(p)
person2 = PersonBuilder().build()
print(person2)