Published on

  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide


  1. 1. Interchangeable objects with polymorphismWhen dealing with type hierarchies, you often want to treat an objectnot as the specific type that it is, but instead as its base type. Thisallows you to write code that doesn’t depend on specific types. In theshape example, methods manipulate generic shapes without respect towhether they’re circles, squares, triangles, or some shape that hasn’teven been defined yet. All shapes can be drawn, erased, and moved,so these methods simply send a message to a shape object; they don’tworry about how the object copes with the message. FeedbackSuch code is unaffected by the addition of new types, and adding newtypes is the most common way to extend an object-oriented programto handle new situations. For example, you can derive a new subtypeof shape called pentagon without modifying the methods that deal onlywith generic shapes. This ability to easily extend a design by derivingnew subtypes is one of the essential ways to encapsulate change. Thisgreatly improves designs while reducing the cost of softwaremaintenance. FeedbackThere’s a problem, however, with attempting to treat derived-typeobjects as their generic base types (circles as shapes, bicycles asvehicles, cormorants as birds, etc.). If a method is going to tell ageneric shape to draw itself, or a generic vehicle to steer, or a genericbird to move, the compiler cannot know at compile time precisely whatpiece of code will be executed. That’s the whole point—when themessage is sent, the programmer doesn’t want to know what piece ofcode will be executed; the draw method can be applied equally to acircle, a square, or a triangle, and the object will execute the propercode depending on its specific type. If you don’t have to know whatpiece of code will be executed, then when you add a new subtype, thecode it executes can be different without requiring changes to themethod call. Therefore, the compiler cannot know precisely what pieceof code is executed, so what does it do? For example, in the followingdiagram the BirdController object just works with generic Birdobjects and does not know what exact type they are. This isconvenient from BirdController’s perspective because it doesn’t haveto write special code to determine the exact type of Bird it’s workingwith or that Bird’s behavior. So how does it happen that, whenmove( ) is called while ignoring the specific type of Bird, the rightbehavior will occur (a Goose runs, flies, or swims, and a Penguinruns or swims)? Feedback
  2. 2. The answer is the primary twist in object-oriented programming: thecompiler cannot make a function call in the traditional sense. Thefunction call generated by a non-OOP compiler causes what is calledearly binding, a term you may not have heard before because you’venever thought about it any other way. It means the compilergenerates a call to a specific function name and the linker resolves thiscall to the absolute address of the code to be executed. In OOP, theprogram cannot determine the address of the code until run time, sosome other scheme is necessary when a message is sent to a genericobject. FeedbackTo solve the problem, object-oriented languages use the concept oflate binding. When you send a message to an object, the code beingcalled isn’t determined until run time. The compiler does ensure thatthe method exists and performs type checking on the arguments andreturn value (a language in which this isn’t true is called weaklytyped), but it doesn’t know the exact code to execute. FeedbackTo perform late binding, Java uses a special bit of code in lieu of theabsolute call. This code calculates the address of the method body,using information stored in the object (this process is covered in greatdetail in Chapter 7). Thus, each object can behave differentlyaccording to the contents of that special bit of code. When you send amessage to an object, the object actually does figure out what to dowith that message. FeedbackIn some languages you must explicitly state that you want a methodto have the flexibility of late-binding properties (C++ uses the virtualkeyword to do this). In these languages, by default, methods are notdynamically bound. In Java, dynamic binding is the default behavior
  3. 3. and you don’t need to remember to add any extra keywords in orderto get polymorphism. FeedbackConsider the shape example. The family of classes (all based on thesame uniform interface) was diagrammed earlier in this chapter. Todemonstrate polymorphism, we want to write a single piece of codethat ignores the specific details of type and talks only to the baseclass. That code is decoupled from type-specific information and thusis simpler to write and easier to understand. And, if a new type—aHexagon, for example—is added through inheritance, the code youwrite will work just as well for the new type of Shape as it did on theexisting types. Thus, the program is extensible. FeedbackIf you write a method in Java (as you will soon learn how to do):Feedbackvoid doStuff(Shape s) { s.erase(); // ... s.draw();}This method speaks to any Shape, so it is independent of the specifictype of object that it’s drawing and erasing. If some other part of theprogram uses the doStuff( ) method: FeedbackCircle c = new Circle();Triangle t = new Triangle();Line l = new Line();doStuff(c);doStuff(t);doStuff(l);the calls to doStuff( ) automatically work correctly, regardless of theexact type of the object. FeedbackThis is a rather amazing trick. Consider the line:doStuff(c);
  4. 4. What’s happening here is that a Circle is being passed into a methodthat’s expecting a Shape. Since a Circle is a Shape it can be treatedas one by doStuff( ). That is, any message that doStuff( ) can sendto a Shape, a Circle can accept. So it is a completely safe and logicalthing to do. FeedbackWe call this process of treating a derived type as though it were itsbase type upcasting. The name cast is used in the sense of casting intoa mold and the up comes from the way the inheritance diagram istypically arranged, with the base type at the top and the derivedclasses fanning out downward. Thus, casting to a base type is movingup the inheritance diagram: “upcasting.” FeedbackAn object-oriented program contains some upcasting somewhere,because that’s how you decouple yourself from knowing about theexact type you’re working with. Look at the code in doStuff( ): Feedback s.erase(); // ... s.draw();Notice that it doesn’t say “If you’re a Circle, do this, if you’re aSquare, do that, etc.” If you write that kind of code, which checks forall the possible types that a Shape can actually be, it’s messy and youneed to change it every time you add a new kind of Shape. Here, youjust say “You’re a shape, I know you can erase( ) and draw( )yourself, do it, and take care of the details correctly.” FeedbackWhat’s impressive about the code in doStuff( ) is that, somehow, theright thing happens. Calling draw( ) for Circle causes different codeto be executed than when calling draw( ) for a Square or a Line, but
  5. 5. when the draw( ) message is sent to an anonymous Shape, thecorrect behavior occurs based on the actual type of the Shape. This isamazing because, as mentioned earlier, when the Java compiler iscompiling the code for doStuff( ), it cannot know exactly what typesit is dealing with. So ordinarily, you’d expect it to end up calling theversion of erase( ) and draw( ) for the base class Shape, and not forthe specific Circle, Square, or Line. And yet the right thing happensbecause of polymorphism. The compiler and run-time system handlethe details; all you need to know right now is that it does happen, andmore importantly, how to design with it. When you send a message toan object, the object will do the right thing, even when upcasting isinvolved. FeedbackAbstract base classes and interfacesOften in a design, you want the base class to present only an interfacefor its derived classes. That is, you don’t want anyone to actuallycreate an object of the base class, only to upcast to it so that itsinterface can be used. This is accomplished by making that classabstract by using the abstract keyword. If anyone tries to make anobject of an abstract class, the compiler prevents it. This is a tool toenforce a particular design. FeedbackYou can also use the abstract keyword to describe a method thathasn’t been implemented yet—as a stub indicating “here is aninterface method for all types inherited from this class, but at thispoint I don’t have any implementation for it.” An abstract methodmay be created only inside an abstract class. When the class isinherited, that method must be implemented, or the inheriting classbecomes abstract as well. Creating an abstract method allows you toput a method in an interface without being forced to provide a possiblymeaningless body of code for that method. FeedbackThe interface keyword takes the concept of an abstract class onestep further by preventing any method definitions at all. The interfaceis a very handy and commonly used tool, as it provides the perfectseparation of interface and implementation. In addition, you cancombine many interfaces together, if you wish, whereas inheritingfrom multiple regular classes or abstract classes is not possible.