25.1. Polymorphism
Problem How handle alternatives based on type? How to create pluggable software components?Alternatives based on type
Conditional variation is a fundamental theme in programs. If a program is designed using if-then-else or case statement conditional logic, then if a new variation arises, it requires modification of the case logicoften in many places. This approach makes it difficult to easily extend a program with new variations because changes tend to be required in several placeswherever the conditional logic exists.Pluggable software components
Viewing components in client-server relationships, how can you replace one server component with another, without affecting the client?Solution When related alternatives or behaviors vary by type (class), assign responsibility for the behaviorusing polymorphic operationsto the types for which the behavior varies.Polymorphism has several related meanings. In this context, it means "giving the same name to services in different objects" [Coad95] when the services are similar or related. The different object types usually implement a common interface or are related in an implementation hierarchy with a common superclass, but this is language-dependent; for example, dynamic binding languages such as Smalltalk do not require this.
Corollary :
Do not test for the type of an object and use conditional logic to perform varying alternatives based on type.Examples
NextGen Problem: How Support Third-Party Tax Calculators?
In the NextGen POS application, there are multiple external third-party tax calculators that must be supported (such as Tax-Master and Good-As-Gold TaxPro); the system needs to be able to integrate with different ones. Each tax calculator has a different interface, so there is similar but varying behavior to adapt to each of these external fixed interfaces or APIs. One product may support a raw TCP socket protocol, another may offer a SOAP interface, and a third may offer a Java RMI interface.What objects should be responsible for handling these varying external tax calculator interfaces?Since the behavior of calculator adaptation varies by the type of calculator, by Polymorphism we should assign the responsibility for adaptation to different calculator (or calculator adapter) objects themselves, implemented with a polymorphic getTaxes operation (see Figure 25.1).
Figure 25.1. Polymorphism in adapting to different external tax calculators.
[View full size image]
Notice the interface and interface realization notation in Figure 25.1.
Monopoly Problem: How to Design for Different Square Actions?
To review, when a player lands on the Go square, they receive $200. There's a different action for landing on the Income Tax square, and so forth. Notice that there is a different rule for different types of squares. Let's review the Polymorphism design principle:
When related alternatives or behaviors vary by type (class), assign responsibility for the behaviorusing polymorphic operationsto the types for which the behavior varies. Corollary : Do not test for the type of an object and use conditional logic to perform varying alternatives based on type.
From the corollary, we know we should not design with case logic (a switch statement in Java or C#) as in the following pseudocode:
Rather, the principle advises us to create a polymorphic operation for each type for which the behavior varies. It varies for the types (classes) RegularSquare, GoSquare , and so on. What is the operation that varies? It's what happens when a player lands on a square. Thus, a good name for the polymorphic operation is landedOn or some variation. Therefore, by Polymorphism, we'll create a separate class for each kind of square that has a different landedOn responsibility, and implement a landedOn method in each. Figure 25.2 illustrates the static-view class design.
// bad design
SWITCH ON square.type
CASE GoSquare: player receives $200
CASE IncomeTaxSquare: player pays tax
…
Figure 25.2. Applying Polymorphism to the Monopoly problem.
[View full size image]
Notice in Figure 25.2 the use of the {abstract} keyword for the landedOn operation.Guideline :
Unless there is a default behavior in the superclass, declare a polymorphic operation in the superclass to be {abstract} .The remaining interesting problem is the dynamic design: How should the interaction diagrams evolve? What object should send the landedOn message to the square that a player lands on? Since a Player software object already knows its location square (the one it landed on), then by the principles of Low Coupling and by Expert, class Player is a good choice to send the message, as a Player already has visibility to the correct square.Naturally, this message should be sent at the end of the takeTurn method. Please review the iteration-1 takeTurn design on p. 355 to see our starting point. Figure 25.3 and Figure 25.4 illustrate the evolving dynamic design.
Figure 25.3. Applying Polymorphism.
[View full size image]
Figure 25.4. The
GoSquare case.- Notice in Figure 25.3 and Figure 25.4 the informal approach to showing the polymorphic cases in separate diagrams when sketching UML. An alternativeespecially when using a UML toolis to use sd and ref frames.
- Notice in Figure 25.3 that the Player object is labeled 'p' so that in the landedOn message we can refer to that object in the parameter list. (You will see in Figure 25.4 that it is useful for the Square to have parameter visibility to the Player .)
- Notice in Figure 25.3 that the Square object is labeled loc (short for 'location') and this is the same label as the return value variable in the getSquare message. This implies they are the same object.
Let's consider each of the polymorphic cases in terms of GRASP and the design issues:
- GoSquare
See Figure 25.4. By low representational gap, the Player should know its cash. Therefore, by Expert, it should be sent an addCash message. Thus the square needs visibility to the Player so it can send the message; consequently, the Player is passed as a parameter 'p' in the landedOn message to achieve parameter visibility. - RegularSquare
See Figure 25.5. In this case, nothing happens. I've informally labeled the diagram to indicate this, though a UML note box could be used as well. In code, the body of this method will be emptysometimes called a NO-OP (no operation) method. Note that to make the magic of polymorphism work, we need to use this approach to avoid special case logic.Figure 25.5. The
RegularSquare case. - IncomeTaxSquare
See Figure 25.6. We need to calculate 10% of the player's net worth. By low representational gap and by Expert, who should know this? The Player . Thus the square asks for the player's worth, and then deducts the appropriate amount.Figure 25.6. The
IncomeTaxSquare case. - GoToJailSquare
See Figure 25.7. Simply, the Player's location must be changed. By Expert, it should receive a setLocation message. Probably, the GoToJailSquare will be initialized with an attribute referencing the JailSquare , so that it can pass this square as a parameter to the Player .Figure 25.7. The
GoToJailSquare case.
UML as Sketch :
Notice in Figure 18.25 on p. 357 for iteration-1 that the Piece remembers the square location but the Player does not, and thus the Player must extract the location from the Piece (to send the getSquare message to the Board ), and then re-assign the new location to the Piece . That's a weak design point, and in this iteration, when the Player must also send the landedOn message to its Square , it becomes even weaker. Why? What's wrong with it? Answer: Problems in coupling .Clearly the Player needs to permanently know its own Square location object rather than the Piece , since the Player keeps collaborating with its Square . You should see this as a refactoring opportunity to improve couplingwhen object A keeps needing the data in object B it implies either 1) object A should hold that data, or 2) object B should have the responsibility (by Expert) rather than object A.Therefore, in iteration-2 I've refined the design so that the Player rather than the Piece knows its square; this is reflected in both the DCD of Figure 25.2 and the interaction diagram of Figure 25.3.In fact, one can even question if the Piece is a useful object in the Design Model. In the real world, a little plastic piece sitting on the board is a useful proxy for a human, because we're big and go to the kitchen for cold beer! But in software, the Player object (being a tiny software blob) can fulfill the role of the Piece .Discussion Polymorphism is a fundamental principle in designing how a system is organized to handle similar variations. A design based on assigning responsibilities by Polymorphism can be easily extended to handle new variations. For example, adding a new calculator adapter class with its own polymorphic getTaxes method will have minor impact on the existing design.
Guideline: When to Design with Interfaces?
Polymorphism implies the presence of abstract superclasses or interfaces in most OO languages. When should you consider using an interface? The general answer is to introduce one when you want to support polymorphism without being committed to a particular class hierarchy. If an abstract superclass AC is used without an interface, any new polymorphic solution must be a subclass of AC, which is very limiting in single-inheritance languages such as Java and C#. As a rule-of-thumb, if there is a class hierarchy with an abstract superclass C1, consider making an interface I1 that corresponds to the public method signatures of C1, and then declare C1 to implement the I1 interface. Then, even if there is no immediate motivation to avoid subclassing under C1 for a new polymorphic solution, there is a flexible evolution point for unknown future cases.Contraindications Sometimes, developers design systems with interfaces and polymorphism for speculative "future-proofing" against an unknown possible variation. If the variation point is definitely motivated by an immediate or very probable variability, then the effort of adding the flexibility through polymorphism is of course rational. But critical evaluation is required, because it is not uncommon to see unnecessary effort being applied to future-proofing a design with polymorphism at variation points that in fact are improbable and will never actually arise. Be realistic about the true likelihood of variability before investing in increased flexibility.Benefits
- Extensions required for new variations are easy to add.
- New implementations can be introduced without affecting clients.
Related Patterns
- Protected Variations
- A number of popular GoF design patterns [GHJV95], which will be discussed in this book, rely on polymorphism, including Adapter, Command, Composite, Proxy, State, and Strategy.
Also Known As; Similar To Choosing Message, Don't Ask "What Kind?"