[ Pobierz całość w formacie PDF ]
.With inspection both cases should be reasonably obvious.Chapter 1: Introduction to Objects 47Interchangeable objectswith polymorphismInheritance usually ends up creating a family of classes, all based on the same uniforminterface.We express this with an inverted tree diagram:5Shapedraw()erase()Circle Square Linedraw() draw() draw()erase( erase() erase()One of the most important things you do with such a family of classes is to treat an objectof a derived class as an object of the base class.This is important because it means you canwrite a single piece of code that ignores the specific details of type and talks just to the baseclass.That code is then decoupled from type-specific information, and thus is simpler towrite and easier to understand.And, if a new type a Triangle, for example is addedthrough inheritance, the code you write will work just as well for the new type of Shape asit did on the existing types.Thus the program is extensible.Consider the above example.If you write a function in Java:void doStuff(Shape s) {s.erase();//.s.draw();}This function speaks to any Shape, so it is independent of the specific type of object it sdrawing and erasing.If in some other program we use the doStuff( ) function:Circle c = new Circle();Triangle t = new Triangle();Line l = new Line();doStuff(c);doStuff(t);doStuff(l);5This uses the Unified Notation, which will primarily be used in this book.48 Thinking in Java www.BruceEckel.comThe calls to doStuff( ) automatically work right, regardless of the exact type of the object.This is actually a pretty amazing trick.Consider the line:doStuff(c);What s happening here is that a Circle handle is being passed into a function that sexpecting a Shape handle.Since a Circle is a Shape it can be treated as one by doStuff( ).That is, any message that doStuff( ) can send to a Shape, a Circle can accept.So it is acompletely safe and logical thing to do.We call this process of treating a derived type as though it were its base type upcasting.Thename cast is used in the sense of casting into a mold and the up comes from the way theinheritance diagram is typically arranged, with the base type at the top and the derivedclasses fanning out downward.Thus, casting to a base type is moving up the inheritancediagram: upcasting.An object-oriented program contains some upcasting somewhere, because that s how youdecouple yourself from knowing about the exact type you re working with.Look at the codein doStuff( ):s.erase();//.s.draw();Notice that it doesn t say If you re a Circle, do this, if you re a Square, do that, etc. If youwrite that kind of code, which checks for all the possible types a Shape can actually be, it smessy and you need to change it every time you add a new kind of Shape.Here, you justsay You re a shape, I know you can erase( ) yourself, do it and take care of the detailscorrectly.Dynamic bindingWhat s amazing about the code in doStuff( ) is that somehow the right thing happens.Calling draw( ) for Circle causes different code to be executed than when calling draw( ) fora Square or a Line, but when the draw( ) message is sent to an anonymous Shape, thecorrect behavior occurs based on the actual type that the Shape handle happens to beconnected to.This is amazing because when the Java compiler is compiling the code fordoStuff( ), it cannot know exactly what types it is dealing with.So ordinarily, you d expectit to end up calling the version of erase( ) for Shape, and draw( ) for Shape and not for thespecific Circle, Square, or Line.And yet the right thing happens.Here s how it works.When you send a message to an object even though you don t know what specific type it is,and the right thing happens, that s called polymorphism.The process used by object-orientedprogramming languages to implement polymorphism is called dynamic binding.Thecompiler and run-time system handle the details; all you need to know is that it happensand more importantly how to design with it.Some languages require you to use a special keyword to enable dynamic binding.In C++this keyword is virtual.In Java, you never need to remember to add a keyword becausefunctions are automatically dynamically bound.So you can always expect that when yousend a message to an object, the object will do the right thing, even when upcasting isinvolved.Chapter 1: Introduction to Objects 49Abstract base classes and interfacesOften in a design, you want the base class to present only an interface for its derived classes.That is, you don t want anyone to actually create an object of the base class, only to upcastto it so that its interface can be used.This is accomplished by making that class abstractusing the abstract keyword.If anyone tries to make an object of an abstract class, thecompiler prevents them.This is a tool to enforce a particular design.You can also use the abstract keyword to describe a method that hasn t been implementedyet as a stub indicating here is an interface function for all types inherited from this class,but at this point I don t have any implementation for it. An abstract method may becreated only inside an abstract class.When the class is inherited, that method must beimplemented, or the inherited class becomes abstract as well.Creating an abstract methodallows you to put a method in an interface without being forced to provide a possiblymeaningless body of code for that method.The interface keyword takes the concept of an abstract class one step further by preventingany function definitions at all.The interface is a very useful and commonly-used tool, as itprovides the perfect separation of interface and implementation.In addition, you cancombine many interfaces together, if you wish.(You cannot inherit from more than oneregular class or abstract class.)Object landscapesand lifetimesTechnically, OOP is just about abstract data typing, inheritance and polymorphism, but otherissues can be at least as important.The remainder of this section will cover these issues.One of the most important factors is the way objects are created and destroyed.Where is thedata for an object and how is the lifetime of the object controlled? There are differentphilosophies at work here.C++ takes the approach that control of efficiency is the mostimportant issue, so it gives the programmer a choice.For maximum run-time speed, thestorage and lifetime can be determined while the program is being written, by placing theobjects on the stack (these are sometimes called automatic or scoped variables) or in the staticstorage area.This places a priority on the speed of storage allocation and release, and controlof these can be very valuable in some situations.However, you sacrifice flexibility becauseyou must know the exact quantity, lifetime and type of objects while you re writing theprogram.If you are trying to solve a more general problem such as computer-aided design,warehouse management or air-traffic control, this is too restrictive.The second approach is to create objects dynamically in a pool of memory called the heap.Inthis approach you don t know until run time how many objects you need, what theirlifetime is or what their exact type is.Those are determined at the spur of the moment whilethe program is running.If you need a new object, you simply make it on the heap at thepoint that you need it.Because the storage is managed dynamically, at run time, the amountof time required to allocate storage on the heap is significantly longer than the time to createstorage on the stack.(Creating storage on the stack is often a single assembly instruction tomove the stack pointer down, and another to move it back up
[ Pobierz całość w formacie PDF ]