工厂模式练习

工厂模式练习——简单工厂、工厂方法与抽象工厂

按《Python 设计模式——工厂模式》的建议,通过三道练习巩固:① 简单工厂(根据参数返回不同产品);② 工厂方法(每个产品对应一个工厂子类);③ 抽象工厂(一族产品:主题的按钮+文本框)。每步都有完整可运行代码和验证要点。


练习一:简单工厂(动物工厂)

目的

体会一个工厂方法根据参数决定创建并返回哪种产品;调用方只调 工厂.create(类型)不直接 new 具体类(不写 Dog()、Cat())。

要求

  • 定义 Animal 抽象类,有抽象方法 speak() -> str
  • 实现 DogCat,speak 分别返回 "汪汪""喵喵"
  • 实现 AnimalFactory,提供类方法或静态方法 create(animal_type: str) -> Animal"dog" 返回 Dog 实例,"cat" 返回 Cat 实例,否则抛 ValueError
  • 客户代码:分别调用 AnimalFactory.create(“dog”)AnimalFactory.create(“cat”),验证 speak() 输出正确,且客户代码中没有出现 Dog()、Cat()

参考答案

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self) -> str:
        pass

class Dog(Animal):
    def speak(self) -> str:
        return "汪汪"

class Cat(Animal):
    def speak(self) -> str:
        return "喵喵"

class AnimalFactory:
    @staticmethod
    def create(animal_type: str) -> Animal:
        if animal_type == "dog":
            return Dog()
        elif animal_type == "cat":
            return Cat()
        raise ValueError(f"未知类型: {animal_type}")

# 使用
pet1 = AnimalFactory.create("dog")
print(pet1.speak())  # 汪汪

pet2 = AnimalFactory.create("cat")
print(pet2.speak())  # 喵喵

验证要点

  • create(“dog”) 返回的对象 speak()“汪汪”create(“cat”)“喵喵”
  • 客户只依赖 AnimalAnimalFactory.create(…),没有写 Dog()Cat()
  • 理解:新增一种动物(如 Bird)时,需要改 AnimalFactory.create 的 if-else,这是简单工厂的缺点(违反开闭原则)。

练习二:工厂方法(每个产品一个工厂子类)

目的

体会抽象工厂 + 具体工厂子类:每个具体工厂只负责创建一种产品;新增产品时只需新增产品类 + 新工厂类不用改原有工厂代码(符合开闭原则)。

要求

  • 保留 AnimalDogCat 定义。
  • 定义 AnimalFactory 抽象类,有抽象方法 create_animal() -> Animal
  • 实现 DogFactoryCatFactory,分别返回 Dog()Cat()
  • 写一个函数 def run(factory: AnimalFactory):内部调 factory.create_animal() 并打印 speak() 的结果。
  • 客户代码:用 DogFactory()CatFactory() 各调一次 run,验证输出;再新增 Bird 和 BirdFactory,用 BirdFactory() 调 run,验证没有改 DogFactory、CatFactory 或 run 的代码

参考答案

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self) -> str:
        pass

class Dog(Animal):
    def speak(self) -> str:
        return "汪汪"

class Cat(Animal):
    def speak(self) -> str:
        return "喵喵"

class Bird(Animal):
    def speak(self) -> str:
        return "叽叽"

class AnimalFactory(ABC):
    @abstractmethod
    def create_animal(self) -> Animal:
        pass

class DogFactory(AnimalFactory):
    def create_animal(self) -> Animal:
        return Dog()

class CatFactory(AnimalFactory):
    def create_animal(self) -> Animal:
        return Cat()

class BirdFactory(AnimalFactory):
    def create_animal(self) -> Animal:
        return Bird()

def run(factory: AnimalFactory):
    animal = factory.create_animal()
    print(animal.speak())

# 使用
run(DogFactory())   # 汪汪
run(CatFactory())   # 喵喵
run(BirdFactory())  # 叽叽

验证要点

  • run(DogFactory())run(CatFactory())run(BirdFactory()) 分别输出 汪汪喵喵叽叽
  • 确认:run 只依赖 AnimalFactoryAnimal,不依赖 Dog、Cat、Bird 具体类;加 Bird 时只新增 Bird 和 BirdFactoryDogFactory、CatFactory、run 都没有被修改

练习三:抽象工厂(主题:按钮 + 文本框一族)

目的

体会一个工厂生产“一族”相关产品:暗色主题工厂生产暗色按钮 + 暗色文本框,亮色主题工厂生产亮色按钮 + 亮色文本框;保证同一主题下多个组件风格一致

要求

  • 定义 ButtonTextBox 抽象类,各有 render() -> str 抽象方法。
  • 实现 DarkButtonDarkTextBox(render 返回带 “暗色” 的字符串)、LightButtonLightTextBox(返回带 “亮色” 的字符串)。
  • 定义 ThemeFactory 抽象类,有 create_button() -> Buttoncreate_textbox() -> TextBox 两个抽象方法。
  • 实现 DarkThemeFactoryLightThemeFactory,分别返回对应的按钮和文本框。
  • 写函数 build_ui(factory: ThemeFactory):调用 factory.create_button()factory.create_textbox(),分别 render() 并打印。
  • 客户代码:用 DarkThemeFactory()LightThemeFactory() 各调一次 build_ui,验证输出分别为暗色一族和亮色一族。

参考答案

from abc import ABC, abstractmethod

class Button(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

class TextBox(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

class DarkButton(Button):
    def render(self) -> str:
        return "[暗色按钮]"

class DarkTextBox(TextBox):
    def render(self) -> str:
        return "暗色文本框"

class LightButton(Button):
    def render(self) -> str:
        return "[亮色按钮]"

class LightTextBox(TextBox):
    def render(self) -> str:
        return "亮色文本框"

class ThemeFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button:
        pass

    @abstractmethod
    def create_textbox(self) -> TextBox:
        pass

class DarkThemeFactory(ThemeFactory):
    def create_button(self) -> Button:
        return DarkButton()

    def create_textbox(self) -> TextBox:
        return DarkTextBox()

class LightThemeFactory(ThemeFactory):
    def create_button(self) -> Button:
        return LightButton()

    def create_textbox(self) -> TextBox:
        return LightTextBox()

def build_ui(factory: ThemeFactory):
    btn = factory.create_button()
    txt = factory.create_textbox()
    print(btn.render(), txt.render())

# 使用
build_ui(DarkThemeFactory())   # [暗色按钮] 暗色文本框
build_ui(LightThemeFactory())  # [亮色按钮] 亮色文本框

验证要点

  • build_ui(DarkThemeFactory()) 输出 暗色按钮暗色文本框(同一族)。
  • build_ui(LightThemeFactory()) 输出 亮色按钮亮色文本框(同一族)。
  • 确认:build_ui 只依赖 ThemeFactory,不关心具体是暗色还是亮色;换主题只换工厂,就能得到整族风格一致的产品。

三步汇总与自检

练习 重点 关键点
简单工厂 一个 create(类型) 根据参数返回不同产品;扩展时要改工厂内部 if-else。
工厂方法 抽象工厂 + 每个产品一个工厂子类;扩展时只加新产品类+新工厂类,不改旧代码。
抽象工厂 一个工厂生产一族产品(按钮+文本框);换工厂即换整族风格。

自检问题

  1. 简单工厂工厂方法在“扩展新产品”时有什么不同?
    简单工厂:要改工厂里的 create 方法(加 if-else 或字典项)。工厂方法:只新增一个产品类和一个工厂子类,不改原有工厂类和 run 等调用代码,符合开闭原则。

  2. 抽象工厂适合什么场景?
    适合需要一次创建多个相关对象、且这些对象要风格/系列一致的场景(如一套 UI 主题、一套数据库相关对象)。一个具体工厂负责“一族”产品,换工厂就换整族。

  3. 客户代码应该依赖什么?
    应依赖产品抽象(如 Animal、Button、TextBox)和工厂接口或工厂类(如 AnimalFactory、ThemeFactory),不直接 new 具体产品类,这样便于替换实现和扩展。

做完以上三道练习,再对照《工厂模式》文档,对简单工厂、工厂方法、抽象工厂会掌握得比较扎实。建议先写简单工厂,再写工厂方法并体会“加 Bird 不改旧代码”,最后写抽象工厂做小主题切换,这样对工厂模式会掌握得比较扎实。

发表评论