Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use factory method in python?

I read below code in 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

I have two questions here.

  1. From code of factory we can know it is used to create APIRouter instance. But why we need it? Why we do not just use api_router = ApiRouter() to get the instance?

  2. In __init__ and factory the local_config and global_config are not used. Why we define it in function?

I guess there should be some advantage for using factory instead of constructor. Like the design pattern in JAVA..I hope the answer can illustrate the advantage or the reason. Better with some exapmle

like image 507
Kramer Li Avatar asked Mar 10 '23 13:03

Kramer Li


1 Answers

This is an sharp question. I'm going to take a (hopefully, informed) guess. I say guess because while I've reviewed the OpenStack code in question, it's not my code, nor do I use this particular configuration architecture.

APIRouter() appears in the source code to be the main instance constructor. APIRouter.factory appears primarily in connection to the paste-deploy design for specifying middleware services / processing pipelines with static configuration files.

The industry has a rich history of imagining that processing frameworks of composable modules--plugin architectures writ large--will be a reliable, performant way to build network services with a high likelihood of market success. That dream spans back at least to UNIX System V's STREAMS in the early 1980s. Plugin architectures have often worked--sometimes masterfully. Yet few if any such frameworks have found success, however, independent of products or vendors that were already highly successful for other reasons. So a plugin system for WordPress, nginx, Apache, or PostgreSQL? Capital! A plugin system that's supposed to coordinate across all four, and dozens of other diverse engines besides? Probably "a bridge too far." But my cynicism digresses.

A factory function (or here, factory method) makes sense wherever one might want to decide what type of object to use independent of the inheritance hierarchy. For example, imagine two implementations of an API router: FastAPIRouter is really fast, but only supports REST. StandardAPIRouter is slower, but it supports either REST or SOAP requests. If you call FastAPIRouter() aka FastAPIRouter.__init__(), you can only get a FastAPIRouter instance back. But if you call FastAPIRouter.factory(...) with parameters indicating the need for a SOAP capability, that factory method can choose to hand back a StandardAPIRouter instance instead. You don't get the precise thing you asked for, perhaps, but you get back something that does what you need. That's often much more valuable. Factories are constructors, but ones that can choose the subclass to construct. Standard constructors can't choose.

If you don't like the tight coupling inherent in sibling classes constructing instances of one another, you can imagine a general request to APIRouter.factory(...) that would make a "command decision" which subclass to hand back based on its (presumably superior) understanding of the best implementation to handle the given parameters. There's still coupling...but it provides intelligence and value.

It's like being in a restaurant and asking the wrong server for something. One possible response is "No! This isn't my table." No customer appreciates that. A better response is "Jake is your server. I will get him for you." That's what factory methods can do. They aren't part of the special methods for a class; lacking a specific, rigid constructor function, they can be more accommodating, even substituting a more appropriate object in place of the one you initially requested.

You do occasionally see factories in Python. collections.namedtuple is a great, somewhat magical example. It doesn't just choose a class from a pre-existing fixed list; it actually creates a new class out of whole cloth according to your specifications. But, dynamic languages like Python, Ruby, Perl, and JavaScript don't need factories as often as statically-typed languages like Java. Their constructors have the same constraints Java's do, but the languages also have dynamic typing ("late binding"), duck typing, and developer willingness to use delegation as much as inheritance. That cluster of features gives more degrees of freedom to choose "the right object for the job." Both by technical necessity and cultural choice, Java developers depend much more heavily on the factory pattern for flexibility.

Also consider history: Many of today's middleware developers have spent significant time working with Java, both technically and economically in its massive ecosystem. It's no surprise Java's patterns and practices for "software engineering" realms like testing, dependency injection, and subsystem composition have spread out to other language communities. Once patterns, expectations, and lingo are well-established, they tend to persist.

So it makes sense, technically and otherwise, to use a .factory() constructor in Neutron.

But there's a "but...." Several, actually.

  1. I can't claim to have seen all the Neutron code in the world, but the code I have seen doesn't actually use the factories. APIRouter.factory is more attractive than APIRouter.__init__, with a slightly altered call sequence. Aligning with developer sensibilities and with a standardized, architected call signature--those are valuable in their own right. But that's a narrow technicality, since Neutron could equally have specified the call signature of standard constructors. Current factories don't actually use their parameters or add intelligence. They are pro forma factories, and de facto alternate constructors.

  2. The whole paste-deploy approach is specifying components you want in a configuration file. That leads one to explicitly choose desired classes, limiting the need for and value of flexible "what kind of object should we construct?" choices in code. Just as dynamic languages' degrees of freedom take some shine off factories, config files diminish factories' value in a middleware composition role.

As to your second question, why are the config settings passed in unused? Presumably because APIRouter doesn't need them. A few other modules (e.g. dealing with versioning and IPAM pluggable filters) do seem to use either the local or global config object (albeit only in passing). I chalk the unused config options up much like the factory approach: they're an architected feature of the composition system that is, in practice, not heavily used, and that happens to be unnecessary for APIRouter.

Summary

Factories allow more flexible object creation. The more rigidly-typed and inheritance-focused a language is, the more necessary the factory pattern becomes. They are very sensible as a feature of a plugin framework or extensible server. But in this particular case, factories are extremely lightly and passingly used.

Even if factories aren't much used in the current code, you could argue they make sense because external modules or future versions could use more extensively. Not all architectural elements need to be initially used to have value as part of a framework.

like image 122
Jonathan Eunice Avatar answered Mar 19 '23 05:03

Jonathan Eunice