Adding new features to a class might be as easy as extending an existing class, and adding a few new
methods
and modifying the behavior of others. It is not necessary to rewrite everything. This is where inheritance comes into play. If you have just written a
Person
class, you must consider the fact that you might later want to write an
Employee
class, or a
Vendor
class. Thus, having
Employee
inherit from
Person
might be the best strategy; in this case, the
Person
class is said to be
extensible
. You do not want to design
Person
so that it contains behavior that
prevents
it from being extended by classes such as
Employee
or
Vendor
(
assuming
, of course, that in your design you really intend for other classes to extend
Person
). For example, you would not want to code functionality into an
Employee
class that is specific to supervisory functions. If you did, and then a class that does not require
supervisory
functionality inherited from
Employee
, you would have a problem.
This point touches on the abstraction guideline discussed earlier.
Person
should contain only the data and behaviors that are specific to a person. Other classes can then subclass it and inherit appropriate data and behaviors.
It is important to decide what attributes and methods can be declared as static. Revisit the discussions in Chapter 3 on using the
static
keyword to understand how to design these into your classes.
Making
Names
Descriptive
Earlier we discussed the use of proper documentation and comments. Following a naming convention for your classes, attributes, and methods is a similar subject. There are many naming conventions, and the convention you choose is not as important as choosing one and
sticking
to it. However, when you choose a convention, make sure that when you create classes, attributes, and method names, you not only follow the convention, but make the names descriptive. When someone reads the name, he should be able to tell from the
name
what the object represents.
Good Naming
Make sure that a naming convention makes sense. Often, people go overboard and create conventions that might make sense to them, but are totally incomprehensible to others. Take care when forcing other to conform to a convention. Make sure that the conventions are
sensible
and that everyone involved understands the intent behind them.
Making names descriptive is a good development practice that applies to more than just OO development.
Abstracting
Out Nonportable Code
If you are designing a system that must use nonportable code (that is, the code will only run on a specific hardware platform), you should abstract this code out of the class. By abstracting out, we mean isolating the nonportable code in its own class. For example, if you are writing code to access a serial port, you should create a wrapper class to deal with it. Your class should then send a message to the wrapper class to get the information or services it needs. Do not put the system-dependent code into your primary class (see Figure 5.5).
Figure 5.5. A serial port wrapper.
If the class moves to another hardware system, the way to access the serial port changes, or you want to go to a parallel port, the code in your primary class does not have to change. The only place the code needs to change is in the wrapper class.
Providing a Way to Copy and Compare Objects
Chapter 3 discusses the issue of copying and comparing objects. It is important to understand how objects are copied and compared. You might not want, or expect, a simple bitwise copy or compare operation. You must make sure that your class behaves as expected, and this means you have to
spend
some time designing how objects are
copied
and compared.
Keeping the Scope as Small as Possible
Keeping the scope as small as possible goes hand-in-hand with abstraction and hiding the implementation. The idea is to localize attributes and behaviors as much as possible. In this way, maintaining, testing, and extending a class are much easier.
Keeping Scope as Small as Possible
Minimizing the scope of global
variables
is a good programming style, and not specific to OO programming.
For example, if you have a method that requires a temporary attribute, keep it local. Consider the following code:
public class Math {
int temp=0;
public int swap (int a, int b) {
temp = a;
a=b;
b=temp;
return temp;
}
}
What is wrong with this class? The problem is that the attribute
temp
is only needed within the scope of the
swap()
method. There is no reason for it to be at the class level. Thus, you should move
temp
within the scope of the
swap()
method:
public class Math {
public int swap (int a, int b) {
int temp=0;
temp = a;
a=b;
b=temp;
return temp;
}
}
This is what is
meant
by keeping the scope as small as possible.
A Class Should Be Responsible for Itself
In a training class based on their book,
Java Primer Plus
, Tyma, Torok and Downing propose the class design guideline that all objects should be responsible for acting on
themselves
whenever possible. Consider trying to print a circle.
First, let's use a non-OO example. The print command finds
Circle
and prints it (see Figure 5.6):
print(circle);
Figure 5.6. A non-OO example of a print scenario.
print
,
draw
, and other functions need to have a
case
statement (or something like an
if/else
structure) to determine what to do for the given shape passed. In this case, a separate print routine for each shape could be called.
Every time you add a new shape, all the functions need to add the shape to their
case
statement.
Now let's look at an OO example. By using polymorphism and grouping the
Circle
into a
Shape
category,
Shape
figures out that it is a
Circle
and
knows
how to print itself (see Figure 5.7):
Shape.print(); // Shape is actually a Circle
Figure 5.7. An OO example of a print scenario.