Two principles to help create robust, reusable object-oriented design apps
By Rahul Tyagi
Monday, November 11 2002 12:00 PM
URL: http://www.zdnetasia.com/builder/architect/object/0,39044554,39095921,00.htm
Business needs change on a day-to-day basis, which makes it quite a challenge to
design software applications that quickly adapt. Well-designed software
applications help businesses get ahead of their competitors, but poorly designed
applications make processes rigid and can cost too much to reuse.
To help
you create better-designed applications, I've identified the object-oriented
design (OOD) elements of good and bad designs. Then, I'll introduce two
design principles that can help you create more scalable, robust, and reusable
applications.
Good design Well-designed applications offer software
components that are more robust, more maintainable, and more reusable. Such
applications should be able to adapt changing business needs without affecting
design. For example, a banking application should be able to support new types
of accounts without a change in the existing design.
Three key points of
good design are:
- Maintainability, which is the ease with which a software system or
component can be modified to adapt to changing environments, improve
performance, correct faults, or other attributes. Well-designed applications
require fewer resources for maintenance and changes.
- Reusability, which is the degree to which a software module or
components can be used in more than one computing program or software system.
Reusability of software components helps ensure faster development of software
applications.
- Robustness, which is the stability of software applications in
extreme situations (e.g., maximum load conditions, erroneous user inputs).
Robust applications have less downtime and can reduce maintenance
costs.
Bad designNobody plans to
create ill-designed applications. It often happens because of a lack of
experience or because the app was designed quickly to meet an extremely tight
deadline. Poorly designed applications usually have these problems in
common:
- They're rigid. A design is rigid if it cannot be easily changed. For
example, a single change to heavily interdependent, rigid software could begin a
cascade of changes in dependent packages. When such a program grows in size, the
designers or maintainers cannot predict the extent of that cascade of change,
and the impact of the change cannot be estimated. This makes the cost of the
change impossible to estimate.
- They're fragile. Poorly created programs have a tendency to break in
many places when a single change is made. Simple changes to one part of the
application can lead to failures in other parts that appear to be completely
unrelated. Fixing those problems leads to even more issues, and the maintenance
process begins to resemble a dog chasing its tail. Such fragility greatly
decreases the credibility of the design and maintenance organization, which
leaves users and managers unable to predict the future quality of the product.
- They're not reusable. A design is difficult to reuse when its
desirable parts are highly dependent upon other details, which aren't desired.
If the design is highly interdependent, other designers will also be daunted by
the amount of work necessary to separate the desirable portion of the design
from the parts that aren't reusable. In most such cases, the cost of the
separation is deemed to be higher than the cost of redevelopment of the
design.
To realize the benefits of a well-designed application, you
can follow one of two fundamental principles: the Open-Closed Principle (OCP) or
the Liskov Substitution Principle (LSP).
Open-Closed Principle The Open-Closed Principle
is the most fundamental principle of OOD. According to this principle, software
entities (e.g., classes, modules, and functions) should be open for extension
and closed for modification.
Open for
extension Open for extension means that the behavior of the
application/component can be extended. You can make the application behave in
new and different ways as the requirements of the application change, or to meet
the needs of new applications.
Closed for modification The source code of an
application/component created with this principle is inviolate. No one should be
allowed to make source code changes to it. If someone wants to add new features,
he or she should do so by extending the existing application but definitely not
by changing existing source code.
Sample
program In structured programming, OCP is hard to achieve. However, in
OOD you can follow OCP with the help of an abstraction mechanism. Let's take a
look at an example.
In Listing A, I created a small program that has two shape
objects, a circle and a square, along with methods for drawing those objects.
The code is self-explanatory; the drawAllShapes method checks for the
type of shape (circle or square) and calls the appropriate method for drawing
it.
But this code doesn’t follow the OCP, because to add a new shape
(e.g., a rectangle) to this program, you would need to modify the
ShapeType class, modify the code of the drawAllShapes method, and
add a new class called Rectangle. Adding the new class results in altered code
in various places, which means this program is not closed for modifications.
You'll also notice that it is poorly designed code, because it is rigid to
changes and difficult to upgrade.
In
Listing B, I redesigned this program by abstracting
common functionality of all shapes into the Shape interface. In a
drawAllShapes method, I used polymorphism for drawing all shapes. In this
code, adding a new shape requires the creation of a Rectangle class by extending
the Shape interface. But there is no need to edit the drawAllShapes
method code. Essentially this design follows the OCP; it is open for extension
and closed for modification.
Liskov Substitution Principle According to Barbara
Liskov, who wrote the LSP in the late 1980s, “Programs that use pointers or
references to base classes must be able to use objects of derived classes
without knowing it.”
For example, the code in Listing C is poorly designed and violates LSP. This
method must be aware about every possible subclass of the Shape class and needs
to change if you add a new subclass of Shape. Suppose you want to create a new
shape, Rectangle, and you want to pass the Rectangle object to the method in
Listing C. It can’t draw the rectangle shape without knowing its type. Such a
method violates the principle, because it requires modifications in order to add
a new shape.
The
drawAllShapes method in Listing B conforms to LSP, because
drawAllShapes is able to use objects of derived classes (e.g., Circle or
Rectangle) without knowing them.
Similar in
principle The OCP is the most basic principle; all well-designed
programs should at least conform to this principle. However, LSP actually
follows the OCP. So if you use OCP, you should probably follow the theory of LSP
as well.
|