Dependency Inversion Principle
The OCP is the guiding principle for good Object-Oriented
design. It is easy and it is difficult. The main problem is how you look at
your design. The design typically has two aspects with it. One, you design an
application module to make it work and second, you need to take care
whether your design, and thereby your application, module is reusable, flexible
and robust.
The OCP tells you that the software module should be open
for extension but closed for modification. As you might have already started
thinking, this is a very high level statement and the real problem is how
to achieve this. Well, it comes through practice, experience and constant
inspection of any piece of design, understanding
that how it performs and works
tackles with the
expanding requirements of the application. But even though you are a new
designer, you can follow certain principles to make sure that your design is a
good one.
One of these principles is the Dependency Inversion
Principle, which helps you make your design OCP compliant. Formally stated, the
principle makes two points:
- High level modules should not
depend upon low level modules. Both should depend upon abstractions
- Abstractions should not
depend upon details. Details should depend upon abstractions.
What this
means? Think about a typical software design process. You typically start with
high-level modules. For example, if you are designing a module, which collects
data from a data source and writes to a database, you will try to break down
the architecture in the following way.
- There
is a component which accepts certain objects and writes data to the
database
- There
is a component which reads data from a certain data source and
creates certain suitable objects for the
data writing component.
Look at it like this, your idea started from the point that you need
to write some data to the database and then you think about getting the data in
an acceptable format. There is nothing wrong in this process. This is what your
application looks like from the high-level. But there is a danger if you fail
to transform your thinking into a right design.
Consider, given the above problem, you come up with the
following design in Figure 1.
Figure 1 A
traditional design example
The DataWriter class uses the DataCollector
class. The DataCollector class is responsible for collecting the data
from some data source and passing the required object to the DataWriter
class. The DataWriter class in turn accepts the passed object and uses
another class DatabaseWriter to write data to the database.
Assuming this architecture, the pseudo-code for the above
classes may look like this.
Listing 6 DataCollector.java
public class DataCollector
{
public void collectData(String source) {
//collect the data from source, let us say String data
//initialise a DataWriter object
DataWriter writer = new DataWriter();
//ask the writer object to write the data
writer.writeData(the collected data);
}
}
The pseudo-code for the DataWriter class may look
like this.
Listing 7 DataWriter.java
public class DataWriter {
public void writeData(the data) {
//write the data to the database
DatabaseWriter dw = new DatabaseWriter();
dw.writeToDB(params);
}
}
The pseudo code for the DatabaseWriter can in turn
look like this.
Listing 8 DatabaseWriter.java
public class DatabaseWriter
{
public void writeToDB(params) {
//write physically to the database
}
}
Look at this design, implement it and your application will
run fine. Now there is a problem, your high level component is dependent on the
details of the low level implementation. Clearly, it is tied to the
implementation of the DatabaseWriter object. This makes your design
inflexible. Really, what you want your DataWriter object to be able to
write to any destination. This gives you the ability to reuse your DataWriter
class to write data to any other destination should the application require it
in the future.
One solution may be to put an if-else loop within the DataWriter,
so that it can decide which destination to write to. We have discussed in OCP
that this is the point not at all desirable. This makes your software open for
modification. A better approach of thinking would be:
"We need to make DataWriter object independent of any
specific destination."
How can we do this? Abstraction is the answer. This is the
key for flexible design. Make the abstractions depend on each other, not the
concrete implementations. Look at the following modified design in Figure 2.
Figure 2 A better
design example
In this design, we have abstracted the implementation of the
data writing components. Now the DataWriter class uses the
<<interface>> Writer. There are two concrete implementations
of this interface viz. DatabaseWriter and FleWriter. You can pass
any of the implementations depending on the requirement. Notice, that this
design is flexible because you can add any other Writer to the
application structure should it be required.
New on the Java Boutique:
New Review:
Time Management Made Easy with the Quartz Enterprise Job Scheduler
Why not just use the Java timer API? This open source scheduling
API boasts simplicity, ease-of-integration, a well-rounded feature
set, and it's free!
New Applet:
Reverse Complement
Reverse Complement is a simple applet that converts DNA or RNA
sequences into three useful formats.
Elsewhere on internet.com:
WebDeveloper Java
Lots of Java information on webdeveloper.com
WDVL Java
Thorough Java resource at the Web Developer's Virtual Library.
ScriptSearch Java
Hundreds of free Java code files to download.
jGuru: Your View of the Java Universe
Customizable portal with online training, FAQs, regular news updates, and tutorials.
|