Refactoring a Class

The term refactoring refers to the process of taking existing code that works and reorganizing it, possibly introducing new classes to encapsulate parts of a larger class. In the MBS the Fish class is a candidate for this treatment because it has grown to a large class that encapsulates several aspects of functionality. It is appropriate that the Fish class has responsibility for several operations, moving, breeding, and dying. However, it might simplify the organization of the code if supplementary classes can be used to encapsulate separate responsibilities.

The potential complexity is evident when we consider the possibilities of defining different kinds of fish through inheritance. In chapter four of the MBS inheritance is used to define fish that have different ways of moving, DarterFiah and SlowFish, and in the exercises additional variations such as CircleFish and TurningDarterFish. There are many exercises that can be based on different variations of movement. In addition, as suggested in a couple exercises in chapter 4, we could define fish that have different ways of breeding through inheritance. If we modify the act method slightly by placing the probabilistic decision of whether a fish dies or not in the die method, rather than in the act method, then we could define through inheritance additional variations of fish with different ways of determining whether they die. Suppose that we have the three ways for fish to move as defined in chapter four: the original threeway movement, the slow movement and the darter movement. Suppose in addition that we define fish with three ways of breeding: the original all-or-none breeding, independent breeding where a fish decides whether to place a child in each empty neighbor independently, and breed-only-one, where when a fish breeds, according to a given probability, it produces only one child placed into a randomly selected empty neighbor. Finally suppose that we define two ways that a fish can die: one based on a random choice as in the original fish, a second based on the age of a fish (assuming that the fish has been modified to have an age). In order to get different combinations of different ways of moving, different ways of breeding, and different ways of dying through inheritance, we would get a complex inheritance hierarchy, as shown below. If we add a new movement, we would need to add several classes to get all possibile combinations with breeding and dying behavior. This goes against the philosophy of object-oriented design and programming to minimize the repetition of code.

The Strategy Pattern

The strategy design pattern gives one way one simplifying this situation. We refactor the Fish class to have an instance variable for each type of action. The specific instance variable assigned to a fish by its constructor will provide the "strategy" or algorithm for that action, and different subclasses of the main class or interface specifying that action will provide different strategies. The diagram for this approach, with only movement and breeding behavior, not dying, is shown below.

The lab for this unit gives more details on how to implement this version of the Fish class with the needed supplementary classes for each action. When it is done, a different way of moving can be added by defining only one new class. It will be combined with different ways of breeding through composition rather than through inheritance, as shown in the diagram. The combinatorial explosion of the inheritance-only approach has been tamed by combining composition and inheritance.