为什么在 python 中使用工厂方法?

Why use factory method in python?

我在 openstack neutron 中阅读了以下代码。

class APIRouter(wsgi.Router):

    @classmethod
    def factory(cls, global_config, **local_config):
        return cls(**local_config)

    def __init__(self, **local_config):
        # do something. Not using local_config

我有两个问题。

  1. 从工厂代码我们可以知道它是用来创建APIRouter实例的。但为什么我们需要它?为什么我们不直接使用 api_router = ApiRouter() 来获取实例?

  2. __init__和工厂中,local_configglobal_config没有被使用。为什么我们在函数中定义它?

我想使用工厂而不是构造函数应该有一些优势。喜欢JAVA中的设计模式..希望回答能够说明优点或者原因。用一些例子更好

这是一个尖锐的问题。我将进行(希望是知情的)猜测。我说猜测是因为虽然我审查了有问题的 OpenStack 代码,但它不是我的代码,我也没有使用这种特定的配置架构。

APIRouter()出现在源代码中是主实例构造函数。 APIRouter.factory 主要与 paste-deploy 设计有关,用于指定带有静态配置文件的中间件服务/处理管道。

业界长期以来一直认为可组合模块的处理框架(插件架构写得很大)将是一种可靠、高效的方式来构建网络服务,并且很有可能在市场上取得成功。这个梦想至少可以追溯到 1980 年代初期的 UNIX System V STREAMS。插件架构经常起作用——有时非常巧妙。然而,很少有这样的框架能够成功,但是,独立于由于其他原因已经非常成功的产品或供应商。 WordPress、nginx、Apache 或 PostgreSQL 的插件系统?首都!一个插件系统应该协调所有四个,以及几十个其他不同的引擎?可能 "a bridge too far." 但我的愤世嫉俗离题了。

工厂函数(或此处为工厂方法)在任何可能想要独立于继承层次结构决定使用哪种类型的对象的地方都很有意义。例如,想象一下 API 路由器的两个实现: FastAPIRouter 确实很快,但只支持 REST。 StandardAPIRouter 较慢,但它支持 REST 或 SOAP 请求。如果你调用 FastAPIRouter() 又名 FastAPIRouter.__init__(),你只能 得到一个 FastAPIRouter 实例。但是,如果您使用指示需要 SOAP 功能的参数调用 FastAPIRouter.factory(...),则该工厂方法可以 选择 返回一个 StandardAPIRouter 实例。也许你没有得到你所要求的精确东西,但你得到的东西可以满足你的需要。这通常更有价值。工厂是构造器,但是可以选择subclass来构造。标准构造函数无法选择。

如果您不喜欢同级 classes 中固有的紧凑 class 构造彼此的实例,您可以想象对 APIRouter.factory(...) 的一般请求会 [= =80=] 哪个 subclass 根据其(大概是更高级的)对处理给定参数的最佳实现的理解交还。仍然存在耦合......但它提供了智能和价值。

这就像在一家餐馆里向错误的服务器询问某事。一种可能的回应是 "No! This isn't my table." 没有客户对此表示赞赏。更好的回应是 "Jake is your server. I will get him for you." 这就是工厂方法可以做的。它们不是 class; 的特殊方法的一部分;缺少特定的、严格的构造函数,它们可以更适应,甚至可以用更合适的对象代替您最初请求的对象。

您偶尔会在 Python 看到工厂。 collections.namedtuple 是一个很棒的、有点神奇的例子。它不只是从预先存在的固定列表中选择 class;它实际上根据您的规格从整块布料中创建了一个新的 class。但是,Python、Ruby、Perl 和 JavaScript 等动态语言不像 Java 等静态类型语言那样经常需要工厂。它们的构造函数具有与 Java 相同的约束,但这些语言也具有动态类型 ("late binding")、鸭式类型,并且开发人员愿意像继承一样使用委托。这组功能提供了更多的选择自由度 "the right object for the job." 无论是技术需要还是文化选择,Java 开发人员都更加依赖于 factory pattern 为了灵活性。

还要考虑一下历史:当今的许多中间件开发人员在 Java 的庞大生态系统中的技术和经济方面都花费了大量时间。 Java 针对 "software engineering" 领域(如测试、依赖注入和子系统组合)的模式和实践已经传播到其他语言社区也就不足为奇了。一旦模式、期望和行话确立,它们往往会持续存在。

因此,无论是技术上还是其他方面,在 Neutron 中使用 .factory() 构造函数都是有意义的。

但是实际上有 "but...." 几个。

  1. 我不能说我看过世界上所有的Neutron代码,但我看过的代码实际上并没有使用工厂。 APIRouter.factoryAPIRouter.__init__ 更具吸引力,调用顺序略有改变。与开发人员的敏感性和标准化、架构化的调用签名保持一致——这些本身就很有价值。但这是一种狭隘的技术细节,因为 Neutron 同样可以指定标准构造函数的调用签名。目前的工厂实际上并不使用它们的参数或添加智能。他们是形式工厂,事实上替代构造者。

  2. 整个 paste-deploy 方法是在配置文件中指定所需的组件。这导致人们明确选择所需的 classes,限制了代码中灵活 "what kind of object should we construct?" 选择的需求和价值。正如动态语言的自由度让工厂失去了一些光彩一样,配置文件降低了工厂在中间件组合角色中的价值。

关于你的第二个问题,为什么传入的配置设置没有被使用?大概是因为 APIRouter 不需要它们。一些其他模块(例如处理版本控制和 IPAM 可插入过滤器)似乎确实使用本地或全局配置对象(尽管只是顺便)。我将未使用的配置选项归类为类似于工厂方法:它们是组合系统的架构特征,在实践中并没有被大量使用,而且对于 APIRouter.[=31= 来说恰好是不必要的]

总结

工厂允许更灵活的对象创建。语言类型越严格、越注重继承,工厂模式就越有必要。作为插件框架或可扩展服务器的一个特性,它们是非常明智的。但在这种特殊情况下,工厂被极其轻而易举地使用。

即使工厂在当前代码中使用不多,您也可以说它们是有意义的,因为外部模块或未来版本可以更广泛地使用。并非所有架构元素都需要最初使用才能作为框架的一部分具有价值。