[ Team LiB ] |
22.7 Classes Are Objects: Generic Object FactoriesBecause classes are objects, it's easy to pass them around a program, store them in data structures, and so on. You can also pass classes to functions that generate arbitrary kinds of objects; such functions are sometimes called factories in OOP design circles. They are a major undertaking in a strongly typed language such as C++, but almost trivial in Python: the apply function and syntax we met in Chapter 14 can call any class with any number of constructor arguments in one step, to generate any sort of instance:[3]
def factory(aClass, *args): # varargs tuple return apply(aClass, args) # Call aClass. class Spam: def doit(self, message): print message class Person: def __init__(self, name, job): self.name = name self.job = job object1 = factory(Spam) # Make a Spam. object2 = factory(Person, "Guido", "guru") # Make a Person. In this code, we define an object generator function, called factory. It expects to be passed a class object (any class will do), along with one or more arguments for the class's constructor. The function uses apply to call the function and return an instance. The rest of the example simply defines two classes and generates instances of both by passing them to the factory function. And that's the only factory function you ever need write in Python; it works for any class and any constructor arguments. One possible improvement worth noting: to support keyword arguments in constructor calls, the factory can collect them with a **args argument and pass them as a third argument to apply: def factory(aClass, *args, **kwargs): # +kwargs dict return apply(aClass, args, kwargs) # Call aClass. By now, you should know that everything is an "object" in Python; even things like classes, which are just compiler input in languages like C++. However, as mentioned at the start of Part VI, only objects derived from classes are OOP objects in Python. 22.7.1 Why Factories?So what good is the factory function (besides giving us an excuse to illustrate class objects in this book)? Unfortunately, it's difficult to show you applications of this design pattern, without listing much more code than we have space for here. In general, though, such a factory might allow code to be insulated from the details of dyamically-configured object construction. For instance, recall the processor example presented in the abstract in Chapter 19, and then again as a has-a composition example in this chapter. It accepted reader and writer objects for processing arbitrary data streams. The original version of this example manually passed in instances of specialized classes like FileWriter and SocketReader to customize the data streams being processed; later, we passed in hardcoded file, stream, and formatter objects. In a more dynamic scenario, streams might be configured by external devices such as configuration files or GUIs. In such a dynamic world, we might not be able to hardcode the creation of stream interface objects in our script, but might instead create them at runtime according to the contents of a configuration file. For instance, the file might simply give the string name of a stream class to be imported from a module, plus an optional constructor call argument. Factory-style functions or code may come in handy here, because we can fetch and pass in classes that are not hardcoded in our program ahead of time. Indeed, those classes might not even have existed at all when we wrote our code: classname = ...parse from config file... classarg = ...parse from config file... import streamtypes # Customizable code aclass = getattr(streamtypes, classname) # Fetch from module reader = factory(aclass, classarg) # or aclass(classarg). processor(reader, ...) Here, the getattr built-in is used to fetch a module attribute given a string name again (it's like saying obj.attr, but attr is a string). Because this code snippet assumes a single constructor argument, it doesn't strictly need either factory or apply (we could make an instance with just aclass(classarg)); they may prove more useful in the presence of unknown argument lists. The general factory coding pattern, though, can improve code flexibility. For more details on such things, please consult books that cover OOP design and design patterns. |
[ Team LiB ] |