Why Use Classes?

   

When dealing with classes, it's important to remember that classes do not enable programmers to do anything more than they would be able to do without them. Although it might be significantly more work, you could write all OOP programs structurally.

Classes are used for the same reason large companies are divided into departments and those departments are divided into subdepartments. When a company is faced with organizing hundreds of people around hundreds of tasks, a divide and conquer strategy is the only way to survive. A departmental architecture divides tasks into manageable, and hopefully related , pieces that can be addressed by an appropriate group of staff. If you're in the engineering department, you care about getting your paycheck on time, but you're probably not concerned with how the payroll system knows how to handle paid holidays. If you're in the payroll department, however, you might care a great deal. As far as the rest of the company is concerned , the processing of paychecks has been fully encapsulated within the payroll department. Taking this same idea and moving it into the software arena is what OOP does when responsibility is divided among classes. As with a company department, each class should be able to do one thing and do it well so the rest of the system can be assured that the assigned tasks are carried out.

In OOP, encapsulation is achieved by enclosing data and methods in classes. When done properly, this isolates information so that state can be carefully managed, and it insulates the rest of a program from the details of class implementation. After you have developed a complete class that performs a certain task, you can effectively forget the intricacies of that task and use the class and the methods it provides. Because the class mechanisms are hidden within its methods, even significant changes to the inner workings of the class that might be necessary later do not require you to modify the rest of your program unless the method signatures change.

Beyond encapsulation, classes allow you to change how you think about the elements of your programs. By writing classes that enclose everything associated with particular tasks or entities, you can build types that have meaning in the problem domains in which you work. You are not limited to describing your program elements as integers, Booleans, or any other native data type. Instead of using these relatively indistinct mechanisms and relying on unattached functions to interpret their values appropriately, you can build software with class types that help describe the problem being solved . In the preceding payroll example, a procedural approach could be implemented that maintains holidays as integer day and month values that are compared against the dates in a pay period whenever a paycheck amount is calculated. However, think of how much more expressive a class-based approach could be. Using a Calendar class, holidays could be maintained as a collection of Date objects. To produce a paycheck, each day during a pay period could also be represented as a Date that is queried for whether it's a weekend day or holiday relative to the current Calendar. The processing that must be performed doesn't change significantly between the two approaches, but a system built on classes allows responsibility to be clearly divided and produces entities that support the thought process required to solve the problem.

Although encapsulation frees developers who use a class from having to know the implementation details, it is also a powerful means of preventing accidental corruption of the data. When access to the data members of a class is managed through methods, relationships between members can be maintained so that invalid states do not occur.

Encapsulation and the capability to create your own descriptive types are strong arguments for object-oriented programming, but they're not the only ones. A great deal of the appeal of OOP is in its support for inheritance. You'll learn more about the mechanics of inheritance a little later, but for now think of it as a way to pull common behavior out of multiple classes and implement that behavior in a separate class so that it can be shared.

As an example of inheritance, consider a program that manages the accounts at a bank. Assume the bank offers both checking and savings accounts to its customers. Viewing each type separately, you could write independent code to support the required functionality. However, this is unnecessary effort and a maintenance headache waiting to happen given the common features checking and savings accounts share. A better approach is to factor out the common attributes and behavior shared by the account types and define a class to represent them. You can define a class called GeneralAccount for this purpose with an account balance attribute and methods to check the balance, make a deposit, and make a withdrawal. You won't use this GeneralAccount class to represent an actual account object but you can use it to simplify the development of CheckingAccount and SavingsAccount classes. If checking accounts at this bank do not pay interest, you can inherit the functionality of GeneralAccount when declaring CheckingAccount and be finished. You can then declare SavingsAccount to inherit from GeneralAccount and add a method to compute an interest payment and add it to the current account balance. With this approach, you can focus only on how the account types differ and reflect those differences in the methods you write. Inheritance allows you to build class hierarchies that simplify the task of representing complex structures in a software system.

Note

When new classes inherit the properties of another class, they are referred to as child classes or subclasses . The class from which they are derived is then called a parent or superclass.


Also, with inheritance comes the concept of polymorphism, which is the true mark of an object-oriented programming language. To understand this aspect of OOP, first think of an object's type not as a specific class implementation, but as the set of messages it understands (that is, the methods to which it can respond). Using this definition, objects of the same type can be implemented by different classes. Each of these classes can respond to the same set of messages, but every one can respond with different behavior through its own distinct method implementations . Polymorphism allows you to treat instances of these classes as instances of a type without regard to their particular implementation. For this to work, the classes must share a common superclass that defines the methods that relate them. Code that makes use of a polymorphic type makes method calls on a reference to the superclass and the behavior that results is determined by the particular subclass implementation.

The classic example of polymorphism uses a set of classes found in a simple drawing program. In this example, the program provides a tool palette with circle and rectangle drawing tools. The user selects a tool from the palette, clicks a screen location, and drags the mouse to size and shape a figure when adding to a drawing. In a structured program, the application displays the drawing using a loop that checks each figure created by the user and calls a specific function that knows how to draw that type of shape. This approach works reasonably well until the program users add a requirement that a triangle must also be supported as a drawing tool. A programmer must then locate the drawing loop, and every other place shapes are treated differently based on their type, and update the code to understand triangles . This update is not too burdensome in this simple example, but it illustrates a maintenance and reliability drawback that can get out of hand quickly in a system with any true complexity. With OOP, encapsulation comes into play first as a better approach to this program. The functions for drawing different shape types should be located in the code that defines each shape, namely within CircleShape, RectangleShape, and TriangleShape classes. The drawing functionality should be implemented as a draw method that holds all the knowledge required to draw the associated shape on the screen. This is already an improvement because the behavior of each shape is now encapsulated within the definition of the shapes themselves . This encapsulation significantly reduces the number of places code must be modified if the drawing requirements for a particular shape are later changed.

There's still more, however. A drawback in our system so far is that although the draw methods are encapsulated within classes, the program still has to call a separate method for each shape type when the screen needs to be displayed. This is unnecessary effort because the program does not care about the shape types; it only wants them to be drawn correctly. The capability for a shape to be drawn correctly is a common behavior found in each of the shape classes ( CircleShape, RectangleShape, and TriangleShape ).

This is where polymorphism comes into play. Instead of defining three unrelated classes, you can factor out the common behavior that, in this case, is the capability for a shape to draw itself. The example becomes a true OOP implementation when a GenericShape class with a draw method is defined as a superclass for each of the three shape classes. A GenericShape does not correspond to any real shape, so it has no implementation for its draw method. However, this class does serve a significant purpose by declaring that its subclasses must support a draw method. With this change, the program can keep track of its shapes as GenericShape instances rather than a collection of CircleShape, RectangleShape, and TriangleShape instances. This works because the program doesn't need to know the difference, provided that it can direct a shape of any type to draw itself. When it's time to draw the screen, the draw method of each GenericShape can be called and, through polymorphism, the draw method of the specific shape type is called.

Note

The dynamic behavior possible with polymorphism is the result of a process known as late binding. When the Java compiler encounters a call to a method, it doesn't bind the call to a particular method implementation until runtime (unless the method is declared static or final, which would mean that the implementation is known at compile-time). This is in contrast to early binding, which binds a method call to a specific implementation during compilation.


Caution

In the previous example, although every circle and rectangle is also a shape, a given shape is not necessarily a circle or a rectangle. Thus, although the CircleShape and RectangleShape classes can be treated just like the GenericShape class in Java, you cannot perform an operation such as getRadius that is reserved for the CircleShape class on an instance of the GenericShape class.


   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net