Previous |
Next |
Programming code that works correctly is difficult. It is difficult to think up, hard to test and even with the best of intentions bugs still show up. If you think of the vast sums of money Microsoft has to spend on creating its software, it might be possible to create bug-free code, but despite their resources bugs still show up. Therefore it makes good sense to create code that can be re-used, it means to you get to re-use the mental effort of creating the code, and the chore of testing the code. Historically the need for code re-use has driven the developments in programming techniques. Structured programming with code in separate blocks called functions or sub routines was designed so that a block of code that was tested and known to be working could be cut and paste between applications or even included as a library.
Object Orientation takes this approach a step further in that you can use and re-use objects without even having access to the source code. This reflects the physical world whereby you assemble complex systems such as a computer from simpler self contained components such as the mother board, graphics card, CD Rom drive. If you consider the overall computer system to be an object the the process of assembling a machine may be considered aggregating the parts into a new system. In programming terms the technique of using multiple self contained objects (or components) is known as aggregation or composition.
Because of the huge benefits of inheritance there is a danger that it is seen as the solution to all problems whereas aggregation offers the benefits of fewer restrictions on the programmer. For example using component control palettes in a GUI builder can be seen as an example of aggregation. Unlike with inheritance each component needs to know very little about the GUI building system that it is contained by. To distinguish between aggregation and inheritance it can be informative to try thinking if the relationship is “is a” or “has a”. Take the example of job roles, if a person has the title of chef, should that be based on inheritance, i.e. The person “is-a” chef or aggregation, so that the role of chef is a separate object within the person object. One way to check this relationship is to ask is the relationship ever going to change. In this example the answer is yes, a chef might become a Head Chef, or change to some other role.
Creating correct and re-usable component designs is more of a art than a science and suggestions like I have given are only guidelines, but the key thing to remember is that inheritance is not the only way to re-use objects.
The concept of encapsulation means that the implementation of a class should be separate from the public interface. The benefit being that the implementation can change without causing programs that use it any problems. The terms Encapsulation and data hiding are often used interchangeably, however some people like to make the distinction that encapsulation is a facility of the language whereas data hiding is a design decision. You can read more about that distinction at
http://www.javaworld.com/javaworld/jw-05-2001/jw-0518-encapsulation.html
You can see an analogy of encapsulation by doing a comparison with the controls on motor vehicles. The controls of a car from the 1940's, 60's through to today are basically the same, i.e. Steering wheel, brake and clutch if its a manual gear change. The implementation is hidden from the driver. Thus a modern car might have servo assisted or abs breaking with power assisted steering, but from a drivers point of view the interface remains the same. The convenience factor is the same with software, once you know the public interface of a class you don't care how the internal implementation works, plus the additional benefit that you can change the implementation without it breaking any code that currently uses it.
The example code I have given in this chapter have used the technique of encapsulation. The following code gives a simple example of this
public class Computer{
private String sProcessor;
public String getProcessor(){
return sProcessor;
}
public void setProcessor(String sProcessor){
this.sProcessor=sProcessor;
}
}
Note how the sProcessor String field is marked as private and can only be accessed externally through the getProcessor and setProcessor methods (known as accessors and mutator methods) or get and set methods. It would be possible to create a class with very similar utility simply by marking the sProcessor String as public and allowing it to be accessed and modified directly from outside the class. The problem with that approach is that it would be easy to accidentally modify that field, and if you needed to add any sanity checking” code you would have to do it on an “ad-hoc” basis wherever you accessed the fields.
Encapsulation involves separating the interface of a class from its implementation. This means you can't "accidentally" corrupt the value of a field, you have to use a method to change a value
|
|
Encapsulation involves hiding data of a class and allowing access only through a public interface. |
Do not be mislead into thinking that the access control with the private keyword is to do with security. It is not designed to prevent a programmer getting at variables, it is to help avoid unwanted modification.
The separation of interface and implementation makes it easier to modify the code within a class without breaking any other code that uses it.
For the class designer this leads to the ability to modify a class, knowing that it will not break programs that use it. A class designer can insert additional checking routines for "sanity checks" for the modification of fields. I have worked on insurance projects where it was possible for clients to have an age of less than zero. If such a value is stored in a simple field such as an integer, there is no obvious place to store checking routines. If the age were only accessible via set and get methods it will be possible to insert checks against zero or negative ages in such a way that it will not break existing code. Of course as development continues more situations may be discovered that need checking against.
For the end user of the class it means they do not have to understand how the internals work and are presented with a clearly defined interface for dealing with data. The end user can be confident that updates to the class code will not break their existing code. When you are first learning to write Java code it may seem like unnecessary extra work to create accessor and mutator methods, but it is a good habit to get into as you move from a student of the language to a professional practitioner.
Encapsulation is useful for many different programming problems. For example the JDBC drivers used to access different database engines use the same interface to access databases with very different implementations. If a user decides to switch to a different database, e.g. from access to Oracle, the interface (the calls to the JDBC driver) stays the same but the way the database behind that driver works may be entirely different.
All manipulation of objects is done through references to objects. It is common to see some confusion between references and the objects they refer to. References are an indirect reference to the actual grouping of code and data which is the object. Bruce Eckel makes a beautiful analogy when he says.
“You might imagine this scene as a television (the object) with your remote control (the reference). As long as you’re holding this reference, you have a connection to the television, but when someone says “change the channel” or “lower the volume,” what you’re manipulating is the reference, which in turn modifies the object. If you want to move around the room and still control the television, you take the remote/reference with you, not the television.”
The implication of this is that you never directly manipulate an object reference. Thus you can never use a math operator such as *, - or + on an object reference. References can be assigned to each other provided they are compatible, by compatible I mean of the appropriate type. Thus the following code shows how you can assign “up” the inheritance hierarchy.
/**
*@author Marcus Green
*assigning reference types
*/
class Base{
}
public class RefAssign extends Base{
public static void main(String argv[]){
Base b = new Base();
RefAssign ra = new RefAssign();
/* Will compile because ra is of type b */
b=ra;
/* Will not compile because b is not of type ra */
//ra=b;
}
} Previous |
Next |