Using Interfaces to Reduce
Dependencies
Using Interfaces to Implement
Polymorphism
Create the Interfaces, Place them in
a Package
Realize each Implementation in its
own Package
Physically Place each type of
Package in its own Segment/Module
Lack of Specification of Static
Signatures
Restricted to Public Access Only
Figure 1: Inappropriate Dependency
on a Misplaced Interface
Figure 2: An Even Worse Dependency…
Figure 3: A Cyclic Dependency, The
Worst of all!
Figure 6: Acceptable Implementation
Dependency
Interfaces are the modern programmer’s most useful tool in producing scalable software. Interfaces allow us to create software that can adapt to changing circumstances. Many modern programming languages support the notion of interfaces including Java, C++ and Ada. Here is a sample interface in Java:
public interface IRocketShip
{
static public final String GUIDE_UP = “UP”;
static public final String GUIDE_NE = “NE”;
static public final String GUIDE_SW = “SW”;
public void launch() throws LaunchFailureException;
public void guide( String[] parameters );
public void abort();
}
Interfaces act as a template; a public specification for portions[2] of a class’s capability.
Clients can program to these interfaces by declaring objects without respect to their type, rather only to their interfaces. This is an important point in understanding the power of interfaces – the client should be unaware of the type of class implementing the interface. All they should be exposed to is the interface, which tells them how to use it. Here is how a client might program to the IRocketShip interface, by declaring and initializing a variable of type IRocketShip:
// Obtain a reference to a rocket ship interface implementation
IRocketShip aMyRocketShip = new ACMERocketShip();
// use the services of the Rocket Ship
try
{
aMyRocketShip.launch();
aMyRocketShip.guide(
new String[] { GUIDE_UP, GUIDE_NE, GUIDE_SW } );
}
catch( LaunchFailureException lfe )
{
lfe.printStackTrace();
aMyRocketShip.abort();
return;
}
The interface is used just as if it were declared a class. The interface variable actually is an interface, although a class replaces it when it is first instantiated.
Instantiation (i.e., use of the new operator) is the only point in the code (other than an import statement) that shows any knowledge of the interface’s “true” type. Later, we will see how to break even this dependency, which is a primary benefit of using interfaces. Since an interface can be used to insulate clients from varying implementations (i.e., classes that implement those interfaces), this can automatically reduce dependencies provided the interfaces and implementations are separated into packages and segments/modules appropriately.
In order to use an interface at runtime, there must be at least one implementation of that interface visible. Here is a class that implements the IRocketShip interface:
public class ACMERocketShip implements IRocketShip
{
public void launch() throws LaunchFailureException
{
// launch code goes here…
}
public void guide( String[] parameters )
{
// guide code goes here…
}
public void abort()
{
// abort code goes here…
}
// NOTE: This method is NOT a part of the interface
public void spinOutOfControl()
{
// spin code goes here…
}
}
We say the class ACMERocketShip realizes the IRocketShip interface. Of course, many other classes can implement this same interface, opening the door to the possibility of varying implementations using the same interface. Interfaces enable component-based development, whereby software components can be made to “plug & play” with other software components[3].
“Old School” OO teaches to use inheritance to accomplish polymorphism. Although inheritance is very useful, the problem with widespread inheritance is that of excess dependence. Inheritance is one of the strongest forms of dependency, and rightfully so in a proper design. But over-use of inheritance can lead to brittle code. For example, service sub-classing[4] is better implemented using delegation.
Usually, what we’re really after is polymorphism. This can be accomplished without using inheritance by using interfaces instead. Since a single class can implement multiple interfaces, we implement interfaces instead of inheriting from super classes. For example, let’s have our implementation class implement another interface (ISpinnable) for objects that know how to spin:
public interface ISpinnable
{
public void spin();
}
Now we implement the interface:
public class ACMERocketShip implements IRocketShip, ISpinnable
{
// as before…
public void spin() { // rocket spin code goes here… }
}
The primary benefit of polymorphism is the ability of clients to invoke methods of the same name on objects of different types. As an absurd example, let’s ask Rockets and DoorKnobs to spin (assuming DoorKnob implements the ISpinnable interface).
public final class MakeUmAllSpin
{
public static void main( String args[] )
{
ISpinnable[] aSpinners = SomeService.getSpinnables();
for( int i = 0; i < aSpinners.length; i++ )
{
aSpinners[ i ].spin();
}
aSpinners = null;
}
}
There is still a coupling, but the dependency is lower than inheritance. Clients are bound to what servers can do, not who they are.
Although not advisable as a common practice, interfaces can be “broken” when necessary. If the client knows the name of the actual implementation class and wants to take advantage of that, they may do so by casting the interface to that of the implementation class (or any other implemented interface) like so:
// Cast the interface to an implementation class
ACMERocketShip aMyRocketShip
= (ACMERocketShip)RocketFactory.getFastRocket( “FAST_ACME” );
aMyRocketShip.spinOutOfControl( );
One reason this may be useful is in implementation-specific code that may be used inside a subsystem or façade. Be aware that if abused, this practice lends itself to violating the very benefits of using interfaces. So generally, this practice is to be avoided.
Interfaces are great, and they allow us to build more flexible code, as we have seen. However, nothing is perfect and without forethought, all our effort to design decoupled code will be for naught if we don’t take a few additional steps to ensure success. The problem is dependencies between software segments[5]. If any code in a segment references even the name of a component (e.g., a class) in another segment, a dependency exists. Worse, cyclic dependencies (where two segments reference each other) can occur.
Suppose our interface IRocketShip has two different implementations: one local and one distributed. The local implementation (ACMERocketShip) makes use of services on the local computer. The distributed implementation class (ACMERocketShipMDB) relies on distributed services and actually runs in a separate address space (also on a separate box). Here is a hypothetical distributed implementation:
import javax.ejb.*;
public class ACMERocketShipMDB implements IRocketShip
{
public void launch()
throws LaunchFailureException { // EJB launch code goes here… }
public void guide( String[] parameters ) { // EJB guide code goes here… }
public void abort() { // EJB abort code goes here… }
// NOT a part of the IRocketShip interface
public void ejbCreate() { // EJB code goes here… }
}
We’d like to have two separate deployments. The last thing we want to do is deliver software that is never used. For example, the distributed implementation must run on a J2EE platform. We do not want to install these dependencies on a laptop at a remote site. It turns out that we can use interfaces to solve this. How?
First, we use the logical notion of a UML package to separate these components. In C++ and IDL, we would use namespaces. In Java and Ada, we use packages. If we put them all in the same package, we must deliver them together. That won’t work so let’s separate out the distributed implementation into its own package.
package com.titus.rockets.local;
public class ACMERocketShip implements IRocketShip …
package com.titus.rockets.ejb;
public class ACMERocketShipMDB implements IRocketShip …
This almost solves the problem, but not quite. Unless we logically and physically separate the implementations from the specification, clients and servers will be needlessly bound. Why? Imagine that our client code wants to use the IRocketShip interface. Being excellent client programmers, they program to the interface, not the class. Suppose the IRocketShip interface resides in the com.titus.rockets.local package. The com.titus.rockets.ejb package is now dependent on the com.titus.rockets.local package because the interface resides in the local package (see Figure 1).
Figure 1: Inappropriate Dependency on a Misplaced Interface
This is okay in the sense that the interface is visible, but so are local implementation classes, which are unused in the com.titus.rockets.ejb package. Worse, let’s place the interface in the EJB package instead (see Figure 2).
Figure 2: An Even Worse Dependency…
Now the local package must deploy the entire J2EE platform even though it doesn’t use it! Let’s really botch things up by creating a circular dependency. Suppose our IRocketShip interface specifies a new method as so:
public interface IRocketShip
{
// as before…
public void refuel( Fuel f );
}
In which package should the Fuel class go? It isn’t dependent on EJB, so it shouldn’t go there.
Let’s place the Fuel class in the local package as in Figure 3.
Figure 3: A Cyclic Dependency, The Worst of all!
Congratulations, we have successfully defeated the entire purpose of packaging! So the question is, “Is there a methodology we can follow to reduce dependencies?” Fundamentally, we are asking, “Where should all this stuff go?”
The answer is, “Interfaces need to be in their own package, separate[6] from either implementation.” Let’s try Figure 4:
package com.titus.rockets.specification;
public interface IRocketShip …
Now each implementation package is dependent on the specification, which it should be, but not on the implementation of unused code. The key here is to place
Ø
Interfaces, and Nothing But Interfaces
in the specification package. Implementation packages are now dependent on a single specification in the form of interfaces in another package rather than other implementations as well. “But wait…” you say. “The Fuel class is also in this interface package, violating our self-imposed rule!” In this case, it is okay (for now) because it is shared among implementations.
A more scalable approach would be to specify Fuel as an interface (IFuel) and move the Fuel class out into its own package as in Figure 5.
We might even take this further, depending on how many ways in which RocketFuel could be implemented.
Let’s look at some code. Suppose we want to implement a Factory Pattern. Our goal is to insulate clients from any knowledge of how an object is created. Here is the recipe:
package com.titus.rockets.specification;
import com.titus.rockets.specification.IRocketShip;
public interface IRocketFactory
{
static public final String FAST_ROCKET = "FAST";
public IRocketShip getRocket( String type );
}
package com.titus.rockets.specification;
public interface IRocketShip
{
public static final String GUIDE_UP = "UP";
public static final String GUIDE_NE = "NE";
public static final String GUIDE_SW = "SW";
public void launch() throws LaunchFailureException;
public void guide( String[] parameters );
public void abort();
}
package com.titus.rockets.specification;
public class LaunchFailureException extends Exception
{
public LaunchFailureException() { super(); }
public LaunchFailureException( String msg ) { super( msg ); }
}
Note again that a class (LaunchFailureException) is included in the interface package. This is okay as long as it is unlikely to change or vary a lot, which it isn’t. Further, the interface depends on it anyway.
Recall there are two implementations; one local and one distributed as an EJB.
package com.titus.rockets.local;
import com.titus.rockets.specification.IRocketFactory;
import com.titus.rockets.specification.IRocketShip;
final public class RocketFactory implements IRocketFactory
{
//////// PUBLIC /////////////////////////////////
static public IRocketFactory getInstance()
{
if( null == sInstance )
{
sInstance = new RocketFactory();
}
return sInstance;
}
public IRocketShip getRocket( String type )
{
if( type.equals( IRocketFactory.FAST_ROCKET ) )
{
return new ACMERocketShip();
}
throw new IllegalArgumentException( "Unknown type." );
}
//////// PRIVATE /////////////////////////////////
static private IRocketFactory sInstance;
private RocketFactory()
{
super();
}
}
That was the local factory. Here’s the local implementation class:
package com.titus.rockets.local;
import com.titus.rockets.specification.IRocketShip;
import com.titus.rockets.specification.LaunchFailureException;
final public class ACMERocketShip implements IRocketShip
{
//////// PUBLIC /////////////////////////////////
public void launch() throws LaunchFailureException
{
// launch code goes here…
System.out.println( "... launching locally... " );
}
public void guide( String[] parameters )
{
// guide code goes here…
System.out.println( "... guiding locally... " );
}
public void abort()
{
// abort code goes here…
System.out.println( "... abort locally... " );
}
// NOTE: This method is NOT a part of the interface
public void spinOutOfControl()
{
// spin code goes here…
System.out.println( "... spinning out of control ... " );
}
//////// PACKAGE /////////////////////////////////
/** Creates new ACMERocketShip */
ACMERocketShip()
{
super();
}
}
Now here is the distributed implementation, which has its own factory and implementation class.
package com.titus.rockets.ejb;
import com.titus.rockets.specification.IRocketFactory;
import com.titus.rockets.specification.IRocketShip;
final public class RocketFactory implements IRocketFactory
{
//////// PUBLIC /////////////////////////////////
static public IRocketFactory getInstance()
{
if( null == sInstance )
{
sInstance = new RocketFactory();
}
return sInstance;
}
public IRocketShip getRocket( String type )
{
if( type.equals( IRocketFactory.FAST_ROCKET ) )
{
/**
* Note that we would not normally return a message-driven
* bean, rather it would be deployed for us in an EJB container.
* But this does demonstrate the concept of dependency.
*/
return new ACMERocketShipMDB();
}
throw new IllegalArgumentException( "Unknown type." );
}
//////// PRIVATE /////////////////////////////////
static private IRocketFactory sInstance;
private RocketFactory()
{
super();
}
}
package com.titus.rockets.ejb;
import com.titus.rockets.specification.IRocketShip;
import com.titus.rockets.specification.LaunchFailureException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;
import javax.jms.MessageListener;
import javax.jms.Message;
public final class ACMERocketShipMDB
implements IRocketShip, MessageDrivenBean, MessageListener
{
//////// PUBLIC /////////////////////////////////
public void launch() throws LaunchFailureException
{
// launch code goes here…
System.out.println( "... launching remotely... " );
}
public void guide( String[] parameters )
{
// guide code goes here…
System.out.println( "... launching remotely... " );
}
public void abort()
{
// abort code goes here…
System.out.println( "... launching remotely... " );
}
public void ejbCreate()
{
// EJB code goes here…
System.out.println( "Required by EJB spec " );
}
public void setMessageDrivenContext(
MessageDrivenContext messageDrivenContext ) throws EJBException
{
// EJB code goes here…
System.out.println( "Required by EJB spec " );
mMessageDrivenContext = messageDrivenContext;
}
public void ejbRemove() throws EJBException
{
// EJB code goes here…
System.out.println( "Required by EJB spec " );
}
public void onMessage( Message message )
{
// JMS code goes here…
System.out.println( "Callback Required by JMS spec " );
}
//////// PACKAGE /////////////////////////////////
/** Creates new ACMERocketShipEJB */
ACMERocketShipMDB()
{
super();
}
//////// PRIVATE /////////////////////////////////
private MessageDrivenContext mMessageDrivenContext;
}
Place the com.titus.rockets.specification in one segment, com.titus.rockets.ejb in another, and com.titus.rockets.local in yet another. The bottom line is that it matters where you physically place the interfaces. Interfaces need to have their own segments to effectively reduce client dependencies.
Interface separation should be a “hard-and-fast” rule, but how segments depend on them and non-interface segments will vary. For example, sometimes it’s okay for an implementation package to reference another. This is especially true if they exist within the same subsystem. But even outside this guideline there are valid reasons for such an implementation dependency. Let’s suppose we want to implement our distributed implementation of the IRocketShip interface in terms of the local implementation. Specifically, we might want to reuse the local component as so:
public void onMessage( Message message )
{
// use the services of the Rocket local Ship which is used
// here to implement the distributed interface implementation
try
{
mRocketShip.launch();
mRocketShip.guide(
new String[] { GUIDE_UP, GUIDE_NE, GUIDE_SW } );
}
catch( LaunchFailureException lfe )
{
lfe.printStackTrace();
mRocketShip.abort();
}
}
Note the similarity between this code and that of the original client code – they are identical. In this case, the distributed version of the IRocketShip is a client of the local version! This leads to the dependency diagram in Figure 6.
Figure 6: Acceptable Implementation Dependency
This diagram says that anytime the com.titus.rockets.ejb segment is deployed, the com.titus.rockets.local and the com.titus.rockets.specification segments must also be deployed. However if com.titus.rockets.local is deployed, com.titus.rockets.specification must be deployed, but not com.titus.rockets.ejb. In this case, that’s exactly what is desired.
For clarity, our examples have shown a one-to-one correlation between a package and a segment. This is not how packages are always deployed. In fact, there is a one-to-many relationship between a segment and a package[7]. It is beyond the scope of this paper to address the many ways in which software can be deployed, but we did want to clarify the point that a segment is not necessarily related to a package in a one-to-one manner.
So far we have focused on the server[8] side. Now that each interface (i.e., specification) and class (i.e., implementation) are properly separated, different versions of the same code can be deployed without imposing needless dependencies on clients. Let’s take a look at one of these clients.
Isolation of server-code dependencies is important. Isolating client dependencies is even more so because it has the potential to affect unknown clients in the future. Earlier we had some client code that instantiated an interface using an implementation class (by calling new – see “Programming to an Interface”). Unfortunately, this bound the client to the interface implementation because they used the name of the implementation class to instantiate the interface. For many applications, this client binding is no big deal. The client may even be expecting certain behavior dependent on the implementation of the interface, or it may switch depending on the environment. However in other designs, that single line of code botches the whole plan.
Is there any way to break this last dependency? Let’s try the Factory Pattern. Let’s replace that instantiation line with this line:
// Obtain a reference to a rocket ship
IRocketShip aMyRocketShip = RocketFactory.getRocket( “FAST” );
Note that the new operator is not exposed to client code. By taking advantage of this, clients can become insulated from changes to external creation code they rely on that they did not write. It would be nice if we could use this pattern to reduce client dependencies.
Here’s a package dependency diagram in Figure 7 that adds factories to the mix.
Figure 7: Introducing Factories
Did this solve the problem? No. We can tell by examining the client’s import statements:
import com.titus.rockets.specification.IRocketFactory;
import com.titus.rockets.specification.IRocketShip;
import com.titus.rockets.specification.LaunchFailureException;
import com.titus.rockets.local.RocketFactory;
Note the com.titus.rockets.local.RocketFactory import statement. Even though the names of the factory classes are the same, their fully qualified names are not! In other words, an import statement (or a fully qualified class name) is still required for the Factory. Ultimately, this will point us to a dependency on the implementation class. Unfortunately, there is no way to get around the fact that the implementation class must be referenced at the point of instantiation of the interface. Is there no hope?
Languages that supply meta-programming facilities allow the capability to dynamically invoke methods on dynamically discovered objects. In Java, this facility is known as reflection. If we implement the Factory using reflection, we can parameterize the correct IRocketShip class, which can vary by deployment using say a properties file. Now we can put the RocketFactory concrete class in the same package as the interfaces and off we go!
Figure 8: The Final Dependency Solution, sort of…
Should the reflection mechanism be used in every circumstance? - Of course not. Reflection is slower, but not necessarily significantly slower. Use judgment – don’t place it in a loop, for example. As always, there are trade-offs. If the information being accessed can be done at initialization and better yet, cached, this technique becomes more attractive.
In summary, we have found that interfaces can be used in conjunction with reflection to completely break physical dependencies between segments. This underlines the importance of using interfaces to enhance the deployment and maintenance experience. Interfaces are not a buzzword, nor are they really “new”. They have been consistently implemented across a broad spectrum of programming languages and software architectures for many years. Hopefully this brief(?) discussion has clarified some of the issues related to properly designing for deployment.
Recall that interfaces expose public methods and public final data (i.e., constants). Interfaces force compliance to a well-defined set of signatures, binding software to capability rather than type. Interfaces are so useful that they should be our “weapon of choice”. However, they are not perfect. Some of their limitations include an inability to express static signatures and an inability to express non-public signatures. In addition, use of interfaces encourages strong typing, which can make the enforcement of some designs difficult.
Although interfaces allow for the specification of static public final data, static methods are not allowed. For example, we cannot say:
public interface IRocketShip
{
static public void shutdown(); // won’t compile
}
This restriction forces us into a situation where we must rely on other means (most notably design patterns) to enforce the notion of static (a.k.a. class-wide) interfaces[9].
Some will argue that interfaces are, by definition, public. To expose any lower a level of the interface breaks the encapsulation of the interface because implementation details would be exposed. While there is strong merit to this position, it reduces the power of interfaces as a mechanism to enforce interchangeable implementations. Again, we are forced to rely on inheritance, patterns and idioms to enforce certain types of implementation architectures because we can’t do this:
public interface IRocketShip
{
protected void commonImplementation(); // won’t compile
}
Sometimes it is the notion of an interface or signature that is important, but the need for strong typing outweighs the need for a common signature. Unfortunately, interfaces cannot help us to have strong typing and enforce polymorphism simultaneously because the signature must match the types. For example, suppose we want a polymorphic method called write() to take an argument of the correct type. Strong typing requires us to specify that type in the interface like so:
public interface IWritable
{
public void write( MyClass mc );
}
But your interface needs to look like this:
public interface IWritable
{
public void write( YourClass yc );
}
We want clients to call this same polymorphic operation (i.e., write()), but they cannot use the same signature. Java’s lack of support for dynamic casting makes implementing this impossible without type knowledge on the part of the client[10]. In cases like this, we can use what in Java are known as “marker interfaces”. A marker interface is an interface with no method signatures. At first glance, this practice seems useless because there is no method signature to be enforced. Granted, it is pretty easy to implement a marker interface and then do nothing with it! But the marker interface may be used as a type tag. The java.io.Serializable interface is a marker interface.
Marker interfaces may also be used to alert developers to the fact that a particular design pattern is to be implemented. For example, suppose I want to implement the Singleton GoF pattern. Singleton requires a static method named something like getInstance(). But interfaces do not allow for the specification of static methods – what to do? Create a marker interface like so:
/**
*
* Problem
* Enforce the fact that only one instance of an object exists
*
* Solution
* Return a single object from a static method
*
* Convention: Do not add the word "Singleton" to the end
* of the name of the class
*
*
* static public ISomeInterface getInstance()
* {
* if( sInstance == null ) //sInstance is an ISomeInterface
* {
* // SomeClass implements ISomeInterface
* sInstance = new SomeClass();
* // other code
* }
* return sInstance;
* }
*/
public interface ISingletonPattern
{
}
Although the compiler will not enforce it, the interface can remind us that the class is to be implemented as a Singleton.
[1] May Karl Marx rot in his grave…
[2] We say ‘portions’ because a class can implement multiple interfaces
[3] Microsoft’s COM, COM+ and .NET architectures are entirely built on the concept of interfaces. Unfortunately, they are also bound to Windows platforms only. This is not a failing of the concept of interfaces, rather how Microsoft has implemented the architecture.
[4] Service sub-classing occurs when a super-class provides infrastructure services to subclasses that are unrelated to the nature of the sub-class. For example, adding the ability to send a class to a printer applied to a Person class.
[5] We mean “segment” in the DIICOE sense. In a more general sense, this correlates to modules, DLLs or .so files.
[6] The Ada programming language enforced this same separation within the language using package spec, package bodies and hierarchical library units.
[7] In some segment models, there can be a many-to-many relationship. In DIICOE, it is one-to-many.
[8] By “server”, we mean any class that provides services to other classes – in other words, almost every class! A client then, is a class that uses the services of, or is otherwise dependent on a server class. Again, this could be any and all classes. Most classes are both clients and servers; we are speaking then of different aspects of a class.
[9] That’s okay – pervasive static methods are an indication of poor OO design anyway!
[10] A new language feature of C++ called covariant return types solves this, but it is not available in Java - yet.