text: #!/usr/bin/env python3
  # -*- coding:utf-8 -*-
  # author: bigfoolliu
  
  
  """
  抽象工厂模式
  
  它能创建一系列相关的对象， 而无需指定其具体类。
  https://refactoringguru.cn/design-patterns/abstract-factory
  https://www.jianshu.com/p/7a56b7bafbb9
  
  特点：
      - 用来创建复杂的对象，这个对象由多个小对象组成，而这些'小对象'属于某一个 '系列'
      - 抽象工厂模式在 Python 代码中很常见。 许多框架和程序库会将它作为扩展和自定义其标准组件的一种方式。
      - 识别：可以通过方法来识别该模式——其会返回一个工厂对象
  
      AbstractFactory：抽象工厂角色，如CourseFactory类，它声明了一组用于创建一种产品的方法，每一个方法对应一种产品，如创建视频和创建手记。
      ConcreteFactory：具体工厂角色，如JavaCourseFactory和PythonCourseFactory类，它实现了在抽象工厂中定义的创建产品的方法，生成一组具体产品，这些产品构成了一个产品族，每一个产品都位于某个产品等级结构中。
      AbstractProduce：抽象产品角色，如Video和Article类，它为每种产品声明接口。
      ConcreteProduce：具体产品角色，如JavaVideo、JavaArticle、PythonVideo和PythonArticle类，它定义具体工厂生产的具体产品对象，实现抽象产品接口中声明的业务方法。
  
  
  使用场景：
      - 如果代码需要与多个不同系列的相关产品交互， 但是由于无法提前获取相关信息， 或者出于对未来扩展性的考虑， 你不希望代码基于产品的具体类进行构建
      - 抽象工厂为你提供了一个接口， 可用于创建 '每个系列产品' 的对象。 只要代码通过该接口创建对象， 那么你就不会生成与应用程序已生成的产品类型不一致的产品
      - 如果你有一个基于一组抽象方法的类， 且其主要功能因此变得不明确， 那么在这种情况下可以考虑使用抽象工厂模式
      - 在设计良好的程序中， 每个类仅负责一件事。 如果一个类与多种类型产品交互， 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中
  
      1、客户端（应用层）不依赖于产品类实例如何被创建、实现等细节
      2、强调一系列相关的产品对象（属于同一产品族）一起使用创建对象需要大量重复的代码
      3、提供一个产品类的库，所有的产品以同样的接口出现，从而使客户端不依赖于具体实现
  
  实现方式：
      1.以不同的产品类型与产品变体为维度绘制矩阵
      2.为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口
      3.声明抽象工厂接口， 并且在接口中为所有抽象产品提供一组构建方法。
      4.为每种产品变体实现一个具体工厂类。
      5.在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境， 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。
      6.找出代码中所有对产品构造函数的直接调用， 将其替换为对工厂对象中相应构建方法的调用。
  
  优点：
      - 你可以确保同一工厂生成的产品相互匹配。
      - 你可以避免客户端和具体产品代码的耦合。
      - 单一职责原则。 你可以将产品生成代码抽取到同一位置， 使得代码易于维护。
      - 开闭原则。 向应用程序中引入新产品变体时， 你无需修改客户端代码。
  
  缺点：
      - 由于采用该模式需要向应用中引入众多接口和类， 代码可能会比之前更加复杂。
  """
  
  
  from __future__ import annotations
  from abc import ABC, abstractmethod
  
  
  ###############################################################################
  # 一些抽象的基类，提供基础方法，供其他人继承复写
  ###############################################################################
  
  
  # 1.构建抽象工厂
  class AbstractFactory(ABC):
      """
      抽象工厂
  
      抽象工厂接口声明了一组方法，这些方法返回不同的抽象产品，即系列，一个系列的产品通常是能够相互协作。
      一个产品系列可能有几个变体，但变体之间是不兼容的。
      """
  
      @abstractmethod
      def create_product_a(self) -> AbstractProductA:
          """
          代表一类实体，返回抽象实体
          """
          pass
  
      @classmethod
      def create_product_b(self) -> AbstractProductB:
          pass
  
  
  # 2.构建实体工厂
  class ConcreteFactory1(AbstractFactory):
      """
      实例工厂
  
      实例工厂生产属于单一变体的一系列产品。 工厂方法保证产生的产品是兼容的。
      请注意，具体工厂方法的签名返回一个抽象产品，而在方法内部实例化了一个具体产品。
      """
  
      def create_product_a(self) -> AbstractProductA:
          return ConcreteProductA1()
  
  
  class ConcreteFactory2(AbstractFactory):
      def create_product_b(self) -> AbstractProductB:
          return ConcreteProductB1()
  
  
  ###############################################################################
  # 每个实体都由对应的实体工厂创建，其他人复写
  ###############################################################################
  
  
  # 3.构建抽象实体
  class AbstractProductA(ABC):
      """
      抽象实体
  
      系列每个不同的实例的基础接口，所有的实体必须实现这些接口。
      """
  
      @abstractmethod
      def useful_function_a(self) -> str:
          pass
  
  
  class AbstractProductB(ABC):
      @abstractmethod
      def useful_function_b(self) -> str:
          pass
  
  
  # 4.构建实体，多个，均继承抽象实体，并需要实现其方法
  class ConcreteProductA1(AbstractProductA):
      """
      实体
  
      必须实现所有的父类的抽象方法
      """
  
      def useful_function_a(self) -> str:
          return 'the result of the product a'
  
  
  class ConcreteProductB1(AbstractProductB):
      def useful_function_b(self) -> str:
          return 'the result of the product b'
  
  
  ###############################################################################
  # 客户端的调用
  ###############################################################################
  
  def client_code(factory: AbstractFactory) -> None:
      """
      客户端只通过抽象的类型-抽象工厂以及抽象实体 来和工厂协作，这样的话，就可以通过传递进来任何的工厂
      的子类到客户端而不会重新修改客户端代码
      """
      product_a = factory.create_product_a()
      ret = product_a.useful_function_a()
      print(ret)
  
  
  def main():
      client_code(ConcreteFactory1())
  
  
  if __name__ == '__main__':
      main()
  