You are on page 1of 12

What is a Dependency?

Whenever a class A uses another class or interface B, then A depends on B.


A cannot carry out it's work without B, and A cannot be reused without also
reusing B. In such a situation the class A is called the "dependant" and the
class or interface B is called the "dependency". A dependant depends on its
dependencies.

Two classes that uses each other are called "coupled". The coupling between
classes can be loose or tight, or somewhere in between. The tightness of a
coupling is not binary. It is not either "loose" or "tight". The degrees of
tightness are continuous, not discrete. You can also characterize
dependencies as "strong" or "weak". A tight coupling leads to strong
dependencies, and a loose coupling leads to weak dependencies, or even no
dependencies in some situations.

Dependencies, or couplings, are directional. That A depends on B doesn't


mean that B also depends on A.

Why are Dependencies Bad?


Dependencies are bad because they decrease reuse. Decreased reuse is bad
for many reasons. Often reuse has positive impact on development speed,
code quality, code readability etc.

How dependencies can hurt reuse is best illustrated by an example:

Imagine you have a class CalendarReader that is able to read a calendar


event list from an XML file. The implementation of CalendarReader is
sketched below:

public class CalendarReader {


public List readCalendarEvents(File calendarEventFile){
//open InputStream from File and read calendar events.
}
}

The method readCalendarEvents takes a File object as parameter. Thus this


method depends on the File class. This dependency on the File class means
that the CalendarReader is capable only of reading calendar events from local
files in the file system. It cannot read calendar event files from a network
connection, a database or a from a resource on the classpath. You can say
that the CalendarReader is tightly coupled to the File class and thereby the
local file system.

A less tightly coupled implementation would be to exchange the File


parameter with an InputStream parameter, as sketched below:

public class CalendarReader {


public List readCalendarEvents(InputStream calendarEventFile){
//read calendar events from InputStream
}
}

As you may know, an InputStream can be obtained from either a File object, a
network Socket, a URLConnection class, a Class object
(Class.getResourceAsStream(String name)), a column in a database via
JDBC etc. Now the CalendarReader is not coupled to the local file system
anymore. It can read calendar event files from many different sources.

With the InputStream version of the readCalendarEvents() method the


CalendarReader has become more reusable. The tight coupling to the local
file system has been removed. Instead it has been replaced with a
dependency on the InputStream class. The InputStream dependency is more
flexible than the File class dependency, but that doesn't mean that the
CalendarReader is 100% reusable. It still cannot easily read data from a NIO
Channel, for instance.
Dependency Types
A dependency isn't just a dependency. There are several types of
dependencies. Each type leads to more or less flexibility in the code. The
dependency types are:

Class Dependencies

Interface Dependencies

Method / Field Dependencies

Class dependencies are dependencies on classes. For instance, the method


in the below code box takes a String as parameter. Therefore it depends on
the String class.

public byte[] readFileContents(String fileName){


//open the file and return the contents as a byte array.
}

Interface dependencies are dependencies on interfaces. For instance, the


method in the below code box takes a CharSequence as parameter.
CharSequence is a standard Java interface (in the java.lang package). Both
CharBuffer, String, StringBuffer, and StringBuilder implements the
CharSequence interface, so instances of any of these classes can be used as
parameter to the method.

public byte[] readFileContents(CharSequence fileName){


//open the file and return the contents as a byte array.
}

Method or field dependencies are dependencies on concrete methods or fields


of an object. It doesn't matter what the class of the object is, or what interfaces
it implements, as long as it has a method or field of the required type. The
following method illustrates a method dependency. The method depends on a
method called "getFileName" in the class of the object given as parameter
(fileNameContainer). Note, the dependency isn't visible from the method
declaration!

public byte[] readFileContents(Object fileNameContainer){

Method method = fileNameContainer


.getClass()
.getMethod("getFileName", null);

String fileName = method.invoke(fileNameContainer, null);

//open the file and return the contents as a byte array.


}

Method or field dependencies are common in API's that use reflection to


obtain it's goals. For instance, Butterfly Persistence uses reflection to detect
getters and setters of a class. Without getters and setters Butterfly Persistence
cannot read and write objects of that class to and from database. In that way
Butterfly Persistence depends on getters and setters. Hibernate (a similar
ORM API) can use either getters and setters, or access the fields directly, also
via reflection. That way Hibernate has either method or field dependencies.

Method (or "function") dependencies can also be seen in languages that


support function pointers or method pointers to be passed as parameters to
other methods. For instance, C# Delegates.

Additional Dependency Characteristics


Dependencies have other important characteristics than just the type.
Dependencies can be compile-time, runtime, visible, hidden, direct, indirect,
contextual etc. These additional dependency characteristics will be covered in
the following sections.

Interface Implementation Dependencies


If a class A depends on an interface I, then A does not depend on the
concrete implementation of I. But, A depends on some implementation of I. A
cannot carry out its work without some implementation of I. Therefore,
whenever a class depends on an interface, that class also depends on an
implementation.

The more methods an interface has, the less chance there is that developers
will provide their own implementation for that interface, unless they are
required to. Therefore, the more methods an interface has the larger the
probability is that developers will just stick to the default implementation of that
interface. In other words, the larger and more complex an interface becomes,
the tighter it is coupled to its default implementation!

Because of interface implementation dependencies, you should not add


functionality to an interface blindly. If the functionality could be encapsulated in
its own component, behind its own interface, you should do so.

Below is an example of what this means. The code example shows a tree
node for a hierarchical tree structure.

public interface ITreeNode {


public void addChild(ITreeNode node);
public List<ITreeNode> getChildren();
public ITreeNode getParent();
}

Imagine that you want to be able to count descendents of a given node. At first
you might be tempted to add a countDescendents() method to
the ITreeNode interface. However, if you do so anyone who wants to implement
the ITreeNode interface will also have to implement
the countDescendent() method.

Instead you could implement a DescendentCounter class that can traverse


an ITreeNode instance an count all descendents of that instance.
This DescendentCounter can be reused with different implementations of
the ITreeNode interface. You have just saved your users the trouble of
implementing the countDescendents() method, even if they need to implement
the ITreeNode interface!

Compile-time and Runtime Dependencies

A dependency that can be resolved at compile time is a compile-time


dependency. A dependency that cannot be resolved until runtime is a runtime
dependency. Compile-time dependencies tend to be easier for developers to
see than runtime dependencies, but sometimes runtime dependencies can be
more flexible. For instance, Butterfly Persistence detects getters and setters
of a class at runtime, and maps them automatically to the database table of
that class. This is a very easy way to map classes to database tables.
However, to do so Butterfly Persistence depends on properly named getters
and setters.

Visible and Hidden Dependencies

A visible dependency is a dependency that developers can see from a class's


interface. If a dependency cannot be seen from the class's interface, it is a
hidden dependency.

In the earlier examples, the String and CharSequence dependencies of the


readFileContents() methods are visible dependencies. They are visible from
the method declaration, which is a part of the class's interface. The method
dependencies of the readFileContents() method that take an Object as
parameter, are invisible. You cannot see from the interface if the
readFileContents() method calls the fileNameContainer.toString() to obtain
the file name, or as it actually does, calls the getFileName()method.

Another example of a hidden dependency is the dependency on a static


singleton, or static methods from within a method. You cannot see from the
interface if a class depends on static methods or static singletons.
As you can imagine hidden dependencies can be bad. They are hard to detect
for developers using the classes with the hidden dependencies. They can only
see them by inspecting the code.

This is not the same as saying that you should never use hidden
dependencies. Hidden dependencies are often the result of providing
sensible defaults. For instance, in this example it may not be a problem:

public class MyComponent{

protected MyDependency dependency = null;

public MyComponent(){
this.dependency = new MyDefaultImpl();
}

public MyComponent(MyDependency dependency){


this.dependency = dependency;
}
}

MyComponent has a hidden dependency on MyDefaultImpl as you can see in the


first constructor. But if MyDefaultImpl does not have any dangerous side
effects, then this hidden dependency is not dangerous.

Direct and Indirect Dependencies

A dependency can be either direct or indirect dependency. If a class A uses a


class B then A has a direct dependency on B. If A depends on B, and B
depends on C, then A has an indirect dependency on C. If you cannot use A
without B, and cannot use B without C, then you cannot use A without C
either.

Indirect dependencies are also called chained dependencies, or "transitive


dependencies" (in "Better, Faster, Lighter Java" by Bruce A. Tate and Justin
Gehtland)

Unnecessarily Extensive Dependencies


Sometimes components depend on more information than they need to carry
out their job. For instance, imagine a login component for a web application.
The login component needs only a user name and a password, and will return
the user object, if any, that matches these. The interface could look like this:

public class LoginManager{

public User login(HttpServletRequest request){


String user = request.getParameter("user");
String password = request.getParameter("password");

//read user and return it.


}
}

Calling the component would look like this:

LoginManager loginManager = new LoginManager();


User user = loginManager.login(request);

It looks simple, right? And even if the login method needs more parameters,
you don't need to change the calling code.

But the login method now has what I call an "unnecessarily extensive
dependency" on the HttpServletRequest interface. It depends on more than it
needs to carry out its work. The LoginManageronly needs a user name and a
password to lookup a user, but takes a HttpServletRequest as parameter for
the login method. An HttpServletRequest contains a lot more information than
the LoginManager needs.

The dependency on the HttpServletRequest interface causes two problems:

1. The LoginManager cannot be reused (called) without


an HttpServletRequest instance. This can make unit testing of
the LoginManager harder. You will need a
mock HttpServletRequest instance, which could be a lot of work.

2. The LoginManager requires the names of the user name and password
parameters to be called "user" and "password". This is also an
unnecessary dependency.

A much better interface for the LoginManager's login method would be:

public User login(String user, String password){


//read user and return it.
}

But look what happens to the calling code now:

LoginManager loginManager = new LoginManager();


User user = loginManager.login(
request.getParameter("user"),
request.getParameter("password"));

It gets more complex. For a component that takes 5 request parameters to do


its job, it would look even worse. This is the main reason that developers
create unnecessarily extensive dependencies. To simplify the calling code.

Local and Context Dependencies

When developing applications it is normal to break the application into minor


components. Some of these components are general purpose components,
which could be useful in other applications too. Other components are
application specific and are not of any use outside of the application.

For a general purpose component any classes belonging to the component (or
API) are "local". The rest of the application is the "context". If a general
purpose component depends on application specific classes, this is called a
"context dependency". Context dependencies are bad because it makes the
general purpose component unusable outside of the application too.

It is tempting to think that only a bad OO designer would create context


dependencies, but this is not true. Context dependencies often occur when
developers try to simplify the design of their application. A good example of
this are request processing applications, like message queue connected
applications or web applications.

Imagine an application that receives request in XML form, processes the


requests and sends back a result in XML form. During processing the XML
request is processed by several individual components. Each of these
components need different information, some of that information being
produced by earlier components. It is very tempting to gather the XML file and
all related processing inside some kind of request object, which is passed to
all components in the processing sequence. Processing components can then
read information from that request object, and attach further information too it
for use by later processing components. By taking this request object as
parameter, each of the request processing components depend on this
request object. The request object is application specific, thus causing each
request processing component to have a context dependency.

Standard vs. Custom Class/Interface Dependencies


In many situations it is better for a component to depend on a class or
interface from the standard Java (or C# etc.) packages. These classes and
interfaces are always available to anyone, making it easier to satisfy these
component dependencies. In addition the these classes are less likely to
change and cause your application to fail compilation.

In some situations though, depending on JDK classes is not the best thing to
do. For example, lets say a method needs 4 strings for its configuration. Then
you method takes 4 strings as parameters. An example could be the driver
name, database url, user name and password needed for a database
connection. If all of these 4 strings are always used together, it may be clearer
for the users of that method if you group the 4 strings into a class, and pass
instances of the class around, instead of the 4 strings.

Summary
By now you have seen several different types and characteristics of
dependencies. In general interface dependencies are preferable over class
dependencies. In some situations though, you may find that a class
dependency may be preferable to an interface dependency. Method and field
dependencies can be very useful, but remember that they are also typically
hidden dependencies, and hidden dependencies makes it harder for users of
your component to detect it, and thereby satisfy it.

Interface implementation dependencies are more common than you would


think. I have seen them in many applications and API's. Try to limit them as
much as possible, by keeping interfaces small. At least the interfaces you may
expect the user of the component to implement themselves. Move additional
functionality (like counting etc.) to external components that take an instance
of the interface in question as parameter.

Personally I prefer compile-time dependencies over runtime dependencies,


but in some cases runtime dependencies are more elegant. For instance Mr.
Persister uses runtime dependencies on getters and setters which frees your
pojos from implemententing a persistence interface. Runtime dependencies
can thus sometimes be less invasive than compile-time dependencies.

Hidden dependencies can be dangerous, but as runtime dependencies are


sometimes also hidden dependencies, you may not always have a choice.
Remember that even if a component does not have a direct dependency on
another component it may still have an indirect dependency on it. Although
typically less limiting indirect dependencies are still dependencies.

Try to avoid unnecessarily extensive dependencies. Keep in mind though, that


unnecessarily extensive dependencies often occur when you group multiple
parameters into a class. This is a common refactoring usually carried out to
make the code look simpler, but as you have now seen, this can also cause
unnecessarily extensive dependencies.

A component that is to be reused across many different contexts, should not


have any context dependencies. Meaning it should not depend on any other
components in the context in which it is intially developed and integrated.

This text has only described dependencies. It does not tell you what to do
about them. Other texts on this training site will delve into this topic.

You might also like