Wednesday, 27 November 2019

Design Pattern - Factory Method

Factory Method Pattern

G.O.F. book says following about Factory Method Pattern :

Intent Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Applicability Use the Factory Method pattern when

  • a class can't anticipate the class of objects it must create.
  • a class wants its subclasses to specify the objects it creates.
  • classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate

fmp.png

In [12]:
# code and comments inspired from  : https://refactoring.guru/design-patterns/factory-method/python/example

from abc import ABC, abstractmethod

class Being(ABC):
    """
    The Being interface declares the operations that all concrete beings
    must implement.
    """

    @abstractmethod
    def talk(self) -> str:
        pass
    
"""
Concrete Products provide various implementations of the Product interface.
"""

class CityBeing(Being):
    def talk(self) -> str:
        return "{City Talk : Sorry, Thankyou, I dont have time}"


class WildBeing(Being):
    def talk(self) -> str:
        return "{Wild Talk : Get lost, I hate you, I love you}"
    


class BeingCreator(ABC):
    """
    The BeingCreator class declares the factory method that is supposed to return an
    object of a Being class. The BeingCreator's subclasses usually provide the
    implementation of this method.
    """
    
    def make_noise(self) -> str:
        """
        Despite its name, the BeingCreator's primary responsibility
        is not creating Beings. Usually, it contains some core business logic
        like make_noise, that relies on Product objects, returned by the factory method.
        Subclasses can indirectly change that business logic by overriding the
        factory method and returning a different type of Being from it.
        """
        
        # Call the factory method to create a Being object.
        being = self.create_being()
        # Now, use the Being.
        result = f"BeingCreator: The same creator's code has \
just worked with {being.talk()} ! {being.talk()} ! {being.talk()} !"
        return result
    
    @abstractmethod
    def create_being(self):
        """
        Note that the BeingCreator may also provide some default implementation of
        this factory method.
        """
        pass
    
"""
Concrete BeingCreators override the factory method in order to change the resulting
product's type.
"""

class CityBeingCreator(BeingCreator):
    """
    Note that the signature of the method still uses the abstract Being type,
    even though the concrete being is actually returned from the method. This
    way the BeingCreator can stay independent of concrete being classes.
    """
    def create_being(self) -> Being:
        return CityBeing()


class WildBeingCreator(BeingCreator):
    def create_being(self) -> Being:
        return WildBeing()
In [13]:
def client_code(creator: BeingCreator) -> None:
    """
    The client code works with an instance of a concrete being creator, albeit through
    its base interface. As long as the client keeps working with the being creator via
    the base interface, you can pass it any being creator's subclass.
    """

    print(f"Client: I'm not aware of the being creator's class, but it still works.\n"
          f"{creator.make_noise()}", end="")


if __name__ == "__main__":
    print("App: Launched with the CityBeingCreator.")
    client_code(CityBeingCreator())
    print("\n")

    print("App: Launched with the WildBeingCreator.")
    client_code(WildBeingCreator())
App: Launched with the CityBeingCreator.
Client: I'm not aware of the being creator's class, but it still works.
BeingCreator: The same creator's code has just worked with {City Talk : Sorry, Thankyou, I dont have time} ! {City Talk : Sorry, Thankyou, I dont have time} ! {City Talk : Sorry, Thankyou, I dont have time} !

App: Launched with the WildBeingCreator.
Client: I'm not aware of the being creator's class, but it still works.
BeingCreator: The same creator's code has just worked with {Wild Talk : Get lost, I hate you, I love you} ! {Wild Talk : Get lost, I hate you, I love you} ! {Wild Talk : Get lost, I hate you, I love you} !