Monday, July 16, 2007

Polymorphism -I





Polymorphism is an often-misunderstood concept within the developer community. Outside the community it's often a buzzword used to create an image of intellectual capital accumulating in a vault somewhere. This article will attempt to define, in a very basic way: What polymorphism is What types of polymorphism exist How they can benefit the Java developer and architect in building better systems What Is Polymorphism? Polymorphism is the ability of one object to be treated, or used, like another. In their ACM report Cardelli and Wegner detailed the types of polymorphism and their relationship to each other. To illustrate this relationship, they created the diagram shown below.









Polymorphism is a powerful tool in the developer's arsenal, allowing architectures to be designed and built that will be flexible enough to change with businesses' needs, yet stable enough not to require redesign and rebuild on a regular basis. Since approximately 80% of the cost of software is for maintenance, building polymorphic architectures can greatly reduce the overall cost of developing good software.
Why Should I Care? Polymorphism is extremely useful in designing enterprise-level systems, as you can create and define multiple interface layers that allow outside parties or outside software to interact with your system in a particular way. We all know how helpful it is in system integration to have a published API, but if interior system changes become necessary, the API may have to be republished and integrated systems may have to be reworked. To avoid this, careful consideration should be given to object APIs as well as the overall system APIs during design. One way to ensure that a robust system can deal with objects that have more complex methodologies than were originally intended is to have the object API be polymorphic. This way, the system knows a "face" of the object being passed to it, but doesn't have to be aware of extemporaneous changes.
Types of Polymorphism What follows are some simple examples of polymorphism as it relates to a video game environment. These examples are used to further define and illustrate the different types of polymorphism.
Overloading Overloading polymorphism occurs when a child class overrides the method implementation of the parent class. This type of polymorphism is used when different child classes have different behaviors based on some intrinsic characteristic of the child class. As an example, if milk and vodka are both drink objects, both will have a method called ingest. The results of ingesting milk and vodka are extremely different, but when designing a character for a game, the only thing you'd need to be concerned about is ingesting the drink and displaying the results. This is a very simple example of overloading polymorphism, since each subclass is redefining the ingest method to return results for its particular class type (see Listing 1).
Inclusion Inclusion polymorphism occurs if a child class inherits its method substance from the base or parent class. This enables objects or systems that would previously have used the base class to use the child classes with equivalent results. Inclusion polymorphism is most useful in masking the inherent complexities of child classes from objects or systems that previously dealt with the parent class. As an example, if the drinks in question were water and Perrier, they might share the ingest method from the base class of drink. This would be an example of inclusion or subclassing polymorphism. Regardless of whether the polymorphism in this example is overloading or inclusion, your character in the game only needs to be concerned with ingesting the drink and dealing with the results.
public Class Water extends Drink{public Water(){}}public Class Perrier extends Drink{public Perrier(){}}
Overloading and inclusion are the most common types of polymorphism in Java (along with special cases of overloading polymorphism that relate to interfaces, which we'll discuss later). Parametric and coercion polymorphisms are also used in Java, but to a lesser degree in most implementations.
Relationship Between Inclusion and Overloading Polymorphism Inclusion polymorphism exists when several subclasses use the method of the superclass (parent) to perform whatever action is required.
Overloading polymorphism exists when each subclass defines its own method of action, either because the parent class has declared the method abstract, or because it simply wishes to provide special processing.
Parametric Parametric polymorphism occurs when a class or classes implement methods that are the same in signature except for the parameters passed to the method. This is extremely useful, as one class can handle a great many different types of arguments to a specific method, enabling the class to be used as a bridge between the different types of objects that wish to communicate with your system and the system itself. An example of parametric polymorphism would be if an ElectromagneticRailGun class had multiple methods for fireProjectile - say, one that took a chunk of iron ore and one that took a missile. The results of firing the projectile would be very different depending on the type of projectile that was fired.
public Class ElectromagneticRailGun{public String fireProjectile(IronOre chunk){ return new String("Boy that smarts!");}public String fireProjectile(Missile icbm){return new String("Global Thermonuclear War");}}
Coercion Coercion polymorphism occurs when a primitive or object type is cast or "coerced" into being another primitive type or object type. An example of coercion polymorphism would be if the character display in the game always showed the number of rounds left in the current weapon, but an energy gun might actually measure the amount of charge left in less than one unit increments, say, 42.3. A revolver, on the other hand, might show five bullets left. But the display only shows whole numbers. So, when updating the display for the energy weapon, we might use casting to change the float value of 42.3 to an integer value of 42. This is usually inefficient, and always dangerous since you're losing precision, but it's sometimes necessary.
public Class Display extends java.awt.Label{public Display(){super();}public setBulletsLeft(int in){super.setText(String.valueOf(in));}public setEnergy(float units){setBulletsLeft((int)units);}}
This is usually a really bad idea, and may result in casting exceptions thrown at runtime. A safer way to convert primitive values in Java is to use the conversion interfaces defined with the language itself. The code above can be rewritten to include this methodology as follows:
public Class Display extends java.awt.Label{public Display(){super();}public setBulletsLeft(int in){super.setText(String.valueOf(in));}public setEnergy(float units){public Float f = new Float(units);setBulletsLeft(f.intValue());}}
Object coercion is a larger and more difficult subject, and won't be detailed here. If a Class coercion situation occurs, consider using the instanceof keyword to provide safer, more stable implementations.
public class Processor{public Processor(){}public void process(Parent p){if (p instanceof Child1){//do something}else{//do something else}}}
Multiple Inheritance vs Interfaces In Java there's a special type of overloading polymorphism to examine. It comes about because of the addition of interfaces to the Java language. An interface is usually defined in a separate file, and specifies the API of the object that will implement the interface.
When Java was first introduced, a lot of C++ programmers were concerned by the lack of multiple inheritance in Java. In some OO languages, such as C++, multiple inheritance is allowed. In Java we have only single inheritance, that is, any class can extend only one parent class. A lot of people starting out in Java see this as a handicap, but they usually change their minds once they understand the power of interfaces.
It's possible to use implementation of interfaces to achieve multiple inheritance. Interfaces in Java don't really allow multiple inheritance, since all classes that implement an interface must implement all methods declared by the interface, just as all classes that subclass a parent must implement all abstract methods declared in the parent class or declare them abstract themselves. Interface implementation does allow for a rather unique type of inclusion polymorphism, however. Since any method can take an interface type as an argument, the method can always be sure that whatever kind of object it's receiving will have all the methods declared in the interface, and they will always return the required type of response. Since a class can implement multiple interfaces, the ability of any object to be utilized by multiple methods increases geometrically as the number of interfaces it implements and the number of methods that accept that interface as a parameter type increase.
To use our video game example, molten metal could implement the drinkable interface just as the drink class does, but the results would be drastically different.
public Class MoltenMetal implements Drinkable{public MoltenMetal();public String ingest(){return new String("ouch!!");}}
Parametric Polymorphism in E-Commerce Situations Often in building a fairly robust system you want to expose parts of the API to the general public. You might wish to have logging methods that took the object where the error occurred, or a String containing the error message to be stored or an Exception that would denote what kind of problem occurred. These types of method signatures might look something like:
static public void log(Object o, int level)static public void log(String o, int level)static public void log(Exception e, int level) So sending data to the log, with either the object causing the problem (or a message about the problem) or the Exception that was generated would yield applicable results.
Inclusion Polymorphism in E-Commerce Situations Inclusion polymorphism is used in e-commerce situations to design and build robust systems that don't require major modification when the highly volatile market changes. In designing and building a transaction server, you might want to build one that processes Transaction Data Objects (TDO). But you might need to create new types of TDOs depending on the type of transaction you wished to process. The beauty of inclusion polymorphism in this situation is that the transaction server code would never change, and any class that extended TDO could be passed to the transaction server for processing.
public Class TDO{public TDO(){}//some transaction methods}public class TransactionServer{public TransactionServer(){}public void process(TDO trans){//transaction processing code}}
Overloading Polymorphism in E-Commerce Situations As discussed earlier, interfaces allow for a special type of overloading polymorphism in Java. This becomes extremely useful when we consider that we've built our transaction server and now we want to run it in a CORBA environment. We can use the declared interfaces of our services and extend the base classes supplied by (or created via a tool by) the ORB vendor, and we've got instant CORBA-compliant objects.
Overloading is also useful if you have a specific type of processing that each subclass needs to do, but a lot of "overhead" processing that all classes of that type need to do. As an example, you can declare an abstract method called process in your Base servlet class, which gets called whenever you get a Post request. When you extend the Base class, all the child classes can then process the request in their own way. The base class can contain all the methods to set the return type, put headers and footers into the response, set up connections, read parameter files and do all the other "housekeeping" chores. That way, when you need to create a new servlet, you simply extend the base class and implement one method containing your business logic.
Coercion - There's Usually a Better Way Coercion has limited use in most production environments, due to the dangers of losing precision and possibly a ClassCastException at runtime. If you often find yourself using casting, try using the object translation methodologies mentioned previously or try creating the most generic situation possible, then using the instanceof keyword to determine the actual type of object you're dealing with for specific processing.

No comments: