Python Factory Pattern | Rookie Tutorial
\n\nThe Factory Pattern is a creational design pattern that provides the best way to create objects.
\n\nImagine you go to a restaurant to order food: you don't need to know how the kitchen prepares the food, you just tell the waiter what you want, and the kitchen will make it for you.
\n\nThe Factory Pattern is like such a "kitchen" - it is responsible for creating objects, and you only need to tell it what type of object you want.
\n\nWhy Do We Need the Factory Pattern?
\n\nIn programming, we often need to create objects. If we directly use the new keyword or class constructors in code, it will lead to:
- \n
- High code coupling: Code that creates objects is tightly bound to specific classes \n
- Difficult maintenance: When modifying or adding new object types, multiple places in the code need to be changed \n
- Violation of the Open/Closed Principle: The principle of being open for extension but closed for modification is violated \n
The Factory Pattern solves these problems by encapsulating the object creation process.
\n\n\n\n
Three Types of Factory Patterns
\n\nThe Factory Pattern is mainly divided into three types. Let's understand them through specific examples.
\n\n1. Simple Factory Pattern
\n\nThe Simple Factory Pattern is the most basic factory pattern. It creates different types of objects through a factory class.
\n\nBasic Structure
\n\nExample
\n\nfrom abc import ABC, abstractmethod\n\n# Product interface\nclass Animal(ABC):\n @abstractmethod\n def speak(self):\n pass\n\n# Concrete products\nclass Dog(Animal):\n def speak(self):\n return "Woof!"\n\nclass Cat(Animal):\n def speak(self):\n return "Meow!"\n\nclass Duck(Animal):\n def speak(self):\n return "Quack!"\n\n# Simple factory\nclass AnimalFactory:\n @staticmethod\n def create_animal(animal_type):\n if animal_type == "dog":\n return Dog()\n elif animal_type == "cat":\n return Cat()\n elif animal_type == "duck":\n return Duck()\n else:\n raise ValueError(f"Unknown animal type: {animal_type}")\n\n# Usage example\ndef test_simple_factory():\n factory = AnimalFactory()\n dog = factory.create_animal("dog")\n cat = factory.create_animal("cat")\n duck = factory.create_animal("duck")\n \n print(dog.speak()) # Output: Woof!\n print(cat.speak()) # Output: Meow!\n print(duck.speak()) # Output: Quack!\n\nif __name__ == "__main__":\n test_simple_factory()\n\n\nAdvantages and Disadvantages
\n\nAdvantages:
\n\n- \n
- Client is decoupled from concrete product classes \n
- Separation of responsibilities, easy to maintain \n
Disadvantages:
\n\n- \n
- Adding new products requires modifying the factory class, violating the Open/Closed Principle \n
- The factory class has too many responsibilities, violating the Single Responsibility Principle \n
2. Factory Method Pattern
\n\nThe Factory Method Pattern solves the problems of the Simple Factory Pattern by letting subclasses decide what objects to create.
\n\nBasic Structure
\n\nExample
\n\nfrom abc import ABC, abstractmethod\n\n# Product interface\nclass Button(ABC):\n @abstractmethod\n def render(self):\n pass\n \n @abstractmethod\n def onClick(self):\n pass\n\n# Concrete products\nclass WindowsButton(Button):\n def render(self):\n return "Render Windows-style button"\n \n def onClick(self):\n return "Windows Button clicked"\n\nclass MacButton(Button):\n def render(self):\n return "Render Mac-style button"\n \n def onClick(self):\n return "Mac Button clicked"\n\n# Creator abstract class\nclass Dialog(ABC):\n @abstractmethod\n def createButton(self) -> Button:\n pass\n \n def render(self):\n # Call factory method to create product\n button = self.createButton()\n result = button.render()\n return result\n\n# Concrete creators\nclass WindowsDialog(Dialog):\n def createButton(self) -> Button:\n return WindowsButton()\n\nclass MacDialog(Dialog):\n def createButton(self) -> Button:\n return MacButton()\n\n# Usage example\ndef test_factory_method():\n # Select concrete factory based on configuration\n config = "windows" # Can be read from config file\n if config == "windows":\n dialog = WindowsDialog()\n else:\n dialog = MacDialog()\n \n result = dialog.render()\n print(result)\n\nif __name__ == "__main__":\n test_factory_method()\n\n\nFactory Method Pattern Flow
\n\n3. Abstract Factory Pattern
\n\nThe Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
\n\nBasic Structure
\n\nExample
\n\nfrom abc import ABC, abstractmethod\n\n# Abstract product A\nclass Button(ABC):\n @abstractmethod\n def paint(self):\n pass\n\n# Abstract product B\nclass Checkbox(ABC):\n @abstractmethod\n def paint(self):\n pass\n\n# Concrete product A1\nclass WindowsButton(Button):\n def paint(self):\n return "Render Windows button"\n\n# Concrete product A2\nclass MacButton(Button):\n def paint(self):\n return "Render Mac button"\n\n# Concrete product B1\nclass WindowsCheckbox(Checkbox):\n def paint(self):\n return "Render Windows checkbox"\n\n# Concrete product B2\nclass MacCheckbox(Checkbox):\n def paint(self):\n return "Render Mac checkbox"\n\n# Abstract factory\nclass GUIFactory(ABC):\n @abstractmethod\n def createButton(self) -> Button:\n pass\n \n @abstractmethod\n def createCheckbox(self) -> Checkbox:\n pass\n\n# Concrete factory 1\nclass WindowsFactory(GUIFactory):\n def createButton(self) -> Button:\n return WindowsButton()\n \n def createCheckbox(self) -> Checkbox:\n return WindowsCheckbox()\n\n# Concrete factory 2\nclass MacFactory(GUIFactory):\n def createButton(self) -> Button:\n return MacButton()\n \n def createCheckbox(self) -> Checkbox:\n return MacCheckbox()\n\n# Client code\nclass Application:\n def __init__(self, factory: GUIFactory):\n self.factory = factory\n self.button = None\n self.checkbox = None\n \n def createUI(self):\n self.button = self.factory.createButton()\n self.checkbox = self.factory.createCheckbox()\n \n def paint(self):\n result = []\n if self.button:\n result.append(self.button.paint())\n if self.checkbox:\n result.append(self.checkbox.paint())\n return "n".join(result)\n\n# Usage example\ndef test_abstract_factory():\n # Select factory based on system type\n system_type = "windows" # Can be auto-detected or read from config\n if system_type == "windows":\n factory = WindowsFactory()\n else:\n factory = MacFactory()\n \n app = Application(factory)\n app.createUI()\n print(app.paint())\n\nif __name__ == "__main__":\n test_abstract_factory()\n\n\n\n\n
Comparison of Three Factory Patterns
\n\n| Feature | \nSimple Factory Pattern | \nFactory Method Pattern | \nAbstract Factory Pattern | \n
|---|---|---|---|
| Complexity | \nLow | \nMedium | \nHigh | \n
| Extensibility | \nPoor | \nGood | \nVery Good | \n
| Applicable Scenarios | \nFew object types | \nSingle product family | \nMultiple related product families | \n
| Open/Closed Principle | \nViolated | \nFollowed | \nFollowed | \n
| Dependency | \nDepends on concrete classes | \nDepends on abstract classes | \nDepends on abstract interfaces | \n
\n\n
Practical Application Scenarios
\n\nScenario 1: Database Connection Factory
\n\nExample
\n\nfrom abc import ABC, abstractmethod\nimport sqlite3\nimport mysql.connector\n\n# Database connection interface\nclass DatabaseConnection(ABC):\n @abstractmethod\n def connect(self):\n pass\n \n @abstractmethod\n def execute(self, query):\n pass\n\n# Concrete database connections\nclass SQLiteConnection(DatabaseConnection):\n def __init__(self, db_path):\n self.db_path = db_path\n self.connection = None\n \n def connect(self):\n self.connection = sqlite3.connect(self.db_path)\n return self.connection\n \n def execute(self, query):\n if self.connection:\n cursor = self.connection.cursor()\n cursor.execute(query)\n return cursor.fetchall()\n\nclass MySQLConnection(DatabaseConnection):\n def __init__(self, host, user, password, database):\n self.host = host\n self.user = user\n self.password = password\n self.database = database\n self.connection = None\n \n def connect(self):\n self.connection = mysql.connector.connect(\n host=self.host,\n user=self.user,\n password=self.password,\n database=self.database\n )\n return self.connection\n \n def execute(self, query):\n if self.connection:\n cursor = self.connection.cursor()\n cursor.execute(query)\n return cursor.fetchall()\n\n# Database factory\nclass DatabaseFactory:\n @staticmethod\n def create_connection(db_type, **kwargs):\n if db_type == "sqlite":\n return SQLiteConnection(**kwargs)\n elif db_type == "mysql":\n return MySQLConnection(**kwargs)\n else:\n raise ValueError(f"Unsupported database type: {db_type}")\n\n# Usage example\ndef test_database_factory():\n # Create SQLite connection\n sqlite_conn = DatabaseFactory.create_connection(\n "sqlite",\n db_path="example.db"\n )\n sqlite_conn.connect()\n \n # Create MySQL connection\n mysql_conn = DatabaseFactory.create_connection(\n "mysql",\n host="localhost",\n user="root",\n password="password",\n database="test"\n )\n mysql_conn.connect()\n \n print("Database connection created successfully!")\n\nif __name__ == "__main__":\n test_database_factory()\n\n\nScenario 2: Logger Factory
\n\nExample
\n\nimport logging\nfrom abc import ABC, abstractmethod\nimport sys\n\n# Logger interface\nclass Logger(ABC):\n @abstractmethod\n def info(self, message):\n pass\n \n @abstractmethod\n def error(self, message):\n pass\n \n @abstractmethod\n def debug(self, message):\n pass\n\n# Console logger\nclass ConsoleLogger(Logger):\n def info(self, message):\n print(f"INFO: {message}")\n \n def error(self, message):\n print(f"ERROR: {message}", file=sys.stderr)\n \n def debug(self, message):\n print(f"DEBUG: {message}")\n\n# File logger\nclass FileLogger(Logger):\n def __init__(self, filename):\n self.filename = filename\n \n def info(self, message):\n with open(self.filename, 'a') as f:\n f.write(f"INFO: {message}n")\n \n def error(self, message):\n with open(self.filename, 'a') as f:\n f.write(f"ERROR: {message}n")\n \n def debug(self, message):\n with open(self.filename, 'a') as f:\n f.write(f"DEBUG: {message}n")\n\n# Logger factory\nclass LoggerFactory:\n @staticmethod\n def get_logger(logger_type, **kwargs):\n if logger_type == "console":\n return ConsoleLogger()\n elif logger_type == "file":\n return FileLogger(**kwargs)\n else:\n raise ValueError(f"Unsupported log type: {logger_type}")\n\n# Usage example\ndef test_logger_factory():\n # Create console logger\n console_logger = LoggerFactory.get_logger("console")\n console_logger.info("This is an info message")\n console_logger.error("This is an error message")\n \n # Create file logger\n file_logger = LoggerFactory.get_logger("file", filename="app.log")\n file_logger.info("Information logged to file")\n file_logger.debug("Debug information")\n\nif __name__ == "__main__":\n test_logger_factory()\n\n\n
YouTip