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

Назад

Make More Objects


Метки:

This brings up a general object-oriented design principle that I first heard spoken by Grady Booch: “If the design is too complicated, make more objects.” This is simultaneously counterintuitive and ludicrously simple, and yet it’s the most useful guideline I’ve found. (You might observe that “making more objects” is often equivalent to “add another level of indirection.”) In general, if you find a place with messy code, consider what sort of class would clean that up. Often the side effect of cleaning up the code will be a system that has better structure and is more flexible.

Consider first the place where Trash objects are created, which is a switch statement inside main( ):

# PatternRefactoring/clip1.py
    for(int i = 0 i < 30 i++)
        switch((int)(Math.random() * 3)):
            case 0 :
                bin.add(new
                  Aluminum(Math.random() * 100))
                break
            case 1 :
                bin.add(new
                  Paper(Math.random() * 100))
                break
            case 2 :
                bin.add(new
                  Glass(Math.random() * 100))

This is definitely messy, and also a place where you must change code whenever a new type is added. If new types are commonly added, a better solution is a single method that takes all of the necessary information and produces a reference to an object of the correct type, already upcast to a trash object. In Design Patterns this is broadly referred to as a creational pattern (of which there are several). The specific pattern that will be applied here is a variant of the Factory Method. Here, the factory method is a static member of Trash, but more commonly it is a method that is overridden in the derived class.

The idea of the factory method is that you pass it the essential information it needs to know to create your object, then stand back and wait for the reference (already upcast to the base type) to pop out as the return value. From then on, you treat the object polymorphically. Thus, you never even need to know the exact type of object that’s created. In fact, the factory method hides it from you to prevent accidental misuse. If you want to use the object without polymorphism, you must explicitly use RTTI and casting.

But there’s a little problem, especially when you use the more complicated approach (not shown here) of making the factory method in the base class and overriding it in the derived classes. What if the information required in the derived class requires more or different arguments? “Creating more objects” solves this problem. To implement the factory method, the Trash class gets a new method called factory. To hide the creational data, there’s a new class called Messenger that carries all of the necessary information for the factory method to create the appropriate Trash object (we’ve started referring to Messenger as a design pattern, but it’s simple enough that you may not choose to elevate it to that status). Here’s a simple implementation ofMessenger:

# PatternRefactoring/clip2.py
class Messenger:
    # Must change this to add another type:
    MAX_NUM = 4
    def __init__(self, typeNum, val):
        self.type = typeNum % MAX_NUM
        self.data = val

Messenger object’s only job is to hold information for the factory( ) method. Now, if there’s a situation in which factory( ) needs more or different information to create a new type of Trash object, the factory( ) interface doesn’t need to be changed. The Messenger class can be changed by adding new data and new constructors, or in the more typical object-oriented fashion of subclassing.

The factory( ) method for this simple example looks like this:

# PatternRefactoring/clip3.py
    def factory(messenger):
        switch(messenger.type):
            default: # To quiet the compiler
            case 0:
                return Aluminum(messenger.data)
            case 1:
                return Paper(messenger.data)
            case 2:
                return Glass(messenger.data)
            # Two lines here:
            case 3:
                return Cardboard(messenger.data)

Here, the determination of the exact type of object is simple, but you can imagine a more complicated system in which factory( ) uses an elaborate algorithm. The point is that it’s now hidden away in one place, and you know to come to this place when you add new types.

The creation of new objects is now much simpler in main( ):

# PatternRefactoring/clip4.py
    for(int i = 0 i < 30 i++)
        bin.add(
          Trash.factory(
            Messenger(
              (int)(Math.random() * Messenger.MAX_NUM),
              Math.random() * 100)))

Messenger object is created to pass the data into factory( ), which in turn produces some kind of Trash object on the heap and returns the reference that’s added to the ArrayList bin. Of course, if you change the quantity and type of argument, this statement will still need to be modified, but that can be eliminated if the creation of the Messenger object is automated. For example, an ArrayList of arguments can be passed into the constructor of a Messenger object (or directly into a factory( ) call, for that matter). This requires that the arguments be parsed and checked at run time, but it does provide the greatest flexibility.

You can see from this code what “vector of change” problem the factory is responsible for solving: if you add new types to the system (the change), the only code that must be modified is within the factory, so the factory isolates the effect of that change.