SpecialistOff.NET / Вопросы / Статьи / Фрагменты кода / Резюме / Метки / Помощь / Файлы

Назад

Simple Factory Method


Метки: python

As an example, let’s revisit the Shape system.

One approach is to make the factory a static method of the base class:

# Factory/shapefact1/ShapeFactory1.py
# A simple static factory method.
from __future__ import generators
import random

class Shape(object):
    # Create based on class name:
    def factory(type):
        #return eval(type + "()")
        if type == "Circle": return Circle()
        if type == "Square": return Square()
        assert 0, "Bad shape creation: " + type
    factory = staticmethod(factory)

class Circle(Shape):
    def draw(self): print("Circle.draw")
    def erase(self): print("Circle.erase")

class Square(Shape):
    def draw(self): print("Square.draw")
    def erase(self): print("Square.erase")

# Generate shape name strings:
def shapeNameGen(n):
    types = Shape.__subclasses__()
    for i in range(n):
        yield random.choice(types).__name__

shapes = \
  [ Shape.factory(i) for i in shapeNameGen(7)]

for shape in shapes:
    shape.draw()
    shape.erase()

The factory( ) takes an argument that allows it to determine what type of Shape to create; it happens to be a String in this case but it could be any set of data. The factory( ) is now the only other code in the system that needs to be changed when a new type of Shape is added (the initialization data for the objects will presumably come from somewhere outside the system, and not be a hard-coded array as in the above example).

Note that this example also shows the new Python 2.2 staticmethod( ) technique for creating static methods in a class.

I have also used a tool which is new in Python 2.2 called a generator. A generator is a special case of a factory: it’s a factory that takes no arguments in order to create a new object. Normally you hand some information to a factory in order to tell it what kind of object to create and how to create it, but a generator has some kind of internal algorithm that tells it what and how to build. It “generates out of thin air” rather than being told what to create.

Now, this may not look consistent with the code you see above:

for i in shapeNameGen(7)

looks like there’s an initialization taking place. This is where a generator is a bit strange - when you call a function that contains a yield statement (yieldis a new keyword that determines that a function is a generator), that function actually returns a generator object that has an iterator. This  iterator is implicitly used in the for statement above, so it appears that you are iterating through the generator function, not what it returns. This was done for convenience of use.

Thus, the code that you write is actually a kind of factory, that creates the generator objects that do the actual generation. You can use the generator explicitly if you want, for example:

gen = shapeNameGen(7)
print(gen.next())

So next( ) is the iterator method that’s actually called to generate the next object, and it takes no arguments. shapeNameGen( ) is the factory, and gen is the generator.

Inside the generator-factory, you can see the call to __subclasses__( ), which produces a list of references to each of the subclasses of Shape (which must be inherited from object for this to work). You should be aware, however, that this only works for the first level of inheritance from Item, so if you were to inherit a new class from Circle, it wouldn’t show up in the list generated by __subclasses__( ). If you need to create a deeper hierarchy this way, you must recurse the __subclasses__( ) list.

Also note that in shapeNameGen( ) the statement:

types = Shape.__subclasses__()

Is only executed when the generator object is produced; each time the next( ) method of this generator object is called (which, as noted above, may happen implicitly), only the code in the for loop will be executed, so you don’t have wasteful execution (as you would if this were an ordinary function).