Python Factories for Scalable, Reusable, and Elegant Code
In a real-life factory, the production of identical or similar objects is not done individually but rather streamlined in assembly lines. Similarly, the factory design pattern allows you to create software that is not tied to a specific product but rather can be reused in a number of similar applications. The factory pattern is one of the famous Gof (Gang of four) design patterns . It is a creational design pattern, and it encapsulates object creation inside a class called the factory. This kind of pattern promotes system flexibility by decoupling from the system the how, when, and by whom its objects are created.
A factory is particularly useful when you need to create new types of objects. If object creation is dispersed throughout the code, then you will have to spend time to find the exact places in your code where the type of the object matters. In software engineering terms, this means that you have a significant amount of technical debt to deal with. This is particularly true in machine learning, as explained in. Machine learning can incur gigantic maintenance costs stemming from data dependencies, data drift, and hidden feedback loops. Factories have found their way into machine learning lately, through the creation of feature stores. Feature stores can be online or offline, and their main purpose is to abstract data transformations as they apply to different features and, in this way, enable faster development, better collaboration, efficient model deployment, and scaling.
1.0 An Example of Factories Implementation in Python
Let us look now at a specific example of how to use factories in Python. Let us assume that we want to create a new wardrobe for a son who is fresh out of college and got his first job at a bank as a quantitative financial analyst. Given that he is fluent in Python, he decides to implement a virtual wardrobe index. We will help him with the assignment of a “formality grade” to each outfit because he is perfectly capable of showing up in an important meeting with a T-shirt and ripped jeans.
As shown below, the virtual wardrobe index starts with the creation of two abstract factories, one for footwear(FootWear) and another for clothes (MensClothes). These are abstract classes, and they only serve the purpose of a template. Unlike Java, Python does not have an inherent abstract class type. Instead, it provides a module called abc, that provides the abstract class infrastructure. The colort and colorb variables of the MensClothes class represent the color of the top (shirt, sweater, etc.) and bottom (pants) respectively. Note we assign a default value “Brown” to the color of shoes and top and bottom (always a safe color).
Now let us implement two types of footwear, by defining the concrete classes Boots and Loafers. We inherit from the FootWear class and then override its shoespecs() method to implement specific actions for each concrete class.
Similarly, we create two concrete classes inheriting from MensClothes, SweaterAndCurdoroyPants, and ButtonDownShirtAndDressPants. Each concrete class overrides the abstract class method clothespecs(), in order to specify particulars for each style. Note that the clothespecs() method accepts a FootWear object, in order to match the color of the footwear with the color of the pants.
Abstract class Outfit below is a factory that provides methods for choices of clothes and shoes, choose_clothes() and choose_shoes() respectively, and has an instance attribute, formality.
Concrete class LightColor_ColdAndCasualDay below inherits from abstract class Outfit and has the following methods:
(a) A constructor that specifies the formality and name variables. The formality variable is inherited from Outfit.
(b) An overridden form of the choose_clothes() method of class Outfit, which returns a SweaterAndCorduroyPants object.
(c ) An overridden form of the choose_shoes() method of Outfit, which returns a Boots object.
As we can see, things become more specific as we move up the hierarchy of classes, while the lower-level classes, such as FootWear, MensClothes, and Outfit are agnostic of what subclasses are created, where their objects are created, and how they are aggregated. A similar kind of structure is defined for the DarkColor_WarmSemiFormalDay class below, which defines methods that return a ButtonDownShirtAndDressPants object for clothes and a Loafers object for footwear.
In contrast to the abstract factories, we have seen so far, DarkColor_WarmSemiFormalDay and LightColor_ColdAndCasualDay are concrete factories. How they serve as factories of objects is illustrated in the definition of class Look that follows. Class Look is the orchestrating class that is responsible for object creation. As we can see below, its constructor takes a factory as an argument. In our limited wardrobe, the factory can be either a LightColor_ColdAndCasualDay or DarkColor_WarmSemiFormalDay object. When we create an object of class Look, the choose_clothes() and choose_shoes() of the factory are called which assign values to instance variables clo and sho respectively. These variables are then used in the all-important dress() method, which puts everything together and creates the complete look! This is scalable, reusable code because of the strategically placed object-creation encapsulation and polymorphism.
In the code snippet below, we can see our object creation and the invocation of the dress() method. Notice that we check whether the two objects are equal or whether object g2 is greater than object g1. In the definition of the Look class above, we define the output of the _eq_ operator as the output of the equality comparison of the formalities of the two objects. As we have discussed, our budding financial analyst knows nothing about formal style, so we needed to tell him in a specific way (formality rating), which looks are considered equivalent in terms of formality. So, 3 is the highest formality (business suit), and 0 is no formality (T-shirt and sneakers). The equals operation below returns False and the greater operation returns True because g2 has a higher formality rating (2) than g1 (1).
Finally, in the code snippet below, we import the current virtual wardrobe, which exists as a dictionary in a text file, and append to it the two new looks we created, with the name of the Outfit object as the key, and the formality as the value.
No comments