Dependency Inversion Principle
The Dependency Inversion Principle has been proposed by Robert C.
Martin. It states that:
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.
This principle seeks to "invert" the conventional notion that high
level modules in software should depend upon the lower level modules.
The principle states that high level or low level modules should not
depend upon each other, instead they should depend upon abstractions.
This is best illustrated by an example.
We will consider the design of a three layer protocol stack. The
discussion is divided into three steps:
The following code shows the design of a three layered radio link
control (RLC) protocol stack. The three layers of the protocol are:
- RLC Physical Layer
- RLC Datalink Layer
- RLC Network Layer
All three layers are modeled as classes. A code skeleton for these
classes is presented below. This is not a good design. It has the
following limitations:
- All layers are dependent upon each other. The code for each
layer needs to include the header files for all other layers.
- Layer relationships are hard-coded. You cannot assemble new
protocol by just assembling a new set of layers.
- Design changes in one layer could impact the design of other
layers.
The solution to these problems is to modify the design of layers by
basing it on a common abstraction. This is the subject of the
next section.
Layers |
class RLC_Physical_Layer
{
RLC_Datalink_Layer *m_p_RLC_Datalink_Layer;
public:
void Device_Transmit(Datagram *p_Datagram)
{
Apply_Physical_Layer_Headers(p_Datagram);
Write_Message_To_Device(p_Datagram);
}
void Handle_Device_Receive(Datagram *p_Datagram)
{
Remove_Physical_Layer_Header(p_Datagram);
m_p_RLC_Datalink_Layer->Handle_Physical_Layer_Receive(p_Datagram);
}
};
class RLC_Datalink_Layer
{
RLC_Physical_Layer *m_p_RLC_Physical_Layer;
RLC_Network_Layer *m_p_RLC_Network_Layer;
public:
void Datalink_Transmit(Datagram *p_Datagram)
{
Process_Datalink_Transmit(p_Datagram);
m_p_RLC_Physical_Layer->Device_Transmit(p_Datagram);
}
void Handle_Physical_Layer_Receive(Datagram *p_Datagram)
{
Process_Datalink_Receive(p_Datagram);
m_p_RLC_Network_Layer->Handle_Network_Layer_Receive(p_Datagram);
}
};
class RLC_Network_Layer
{
RLC_Datalink_Layer *m_p_RLC_Datalink_Layer;
Application_Layer *m_p_Application_Layer;
public:
void Network_Transmit(Datagram *p_Datagram)
{
Process_Network_Layer_Transmit(p_Datagram);
m_p_RLC_Datalink_Layer->Datalink_Transmit(p_Datagram);
}
void Handle_Datalink_Layer_Receive(Datagram *p_Datagram)
{
Process_Network_Layer_Receive(p_Datagram);
m_p_Application_Layer->Handle_Application_Receive(p_Datagram);
}
};
|
We now apply the Dependency Inversion Principle to the the above
code. We define an abstract class Protocol_Layer that represents a
generic layer. Important elements of the Protocol_Layer abstraction are:
- The constructor allows you to associate a layer two more layers,
viz. the upper layer and the lower layer.
- A layer receives a message for transmission and invokes the
layer specific processing. After layer specific processing, the
message is passed to the lower layer.
- When a message is received from the lower layer, layer specific
processing is invoked. Once layer handling of the message is
completed, the upper layer is invoked.
The three layers in the above example, will now inherit from the
Protocol_Layer. This design completely decouples the three layers. All
the layers depend upon the abstraction but not on each other. The
advantages are described in the
next section.
Protocol_Layer Abstraction |
// Protocol_Layer abstraction has been defined to decouple the
// different protocol layers. Now the layers only depend upon
// this abstraction.
class Protocol_Layer
{
// Each layer can associate with an upper and lower layer.
// This association is also in terms of the Protocol_Layer
// class. Thus there is no dependency on the actual types
// of individual layers. The only requirement is that they
// should inherit from Protocol_Layer.
Protocol_Layer *m_p_Lower_Layer;
Protocol_Layer *m_p_Upper_Layer;
// Each inheriting layer must override the following methods.
// These methods handle the actual protocol processing.
virtual void Process_Transmit(Datagram *p_Datagram) = 0;
virtual void Process_Receive(Datagram *p_Datagram) = 0;
public:
// Create a protocol layer with the associated upper and
// lower layers.
Protocol_Layer(Protocol_Layer *p_Lower_Layer,
Protocol_Layer *p_Upper_Layer)
{
m_p_Lower_Layer = p_Lower_Layer;
m_p_Upper_Layer = p_Upper_Layer;
}
// Process and transmit the datagram passed by higher layer.
void Transmit(Datagram *p_Datagram)
{
Process_Transmit(p_Datagram);
if (m_p_Lower_Layer)
{
m_p_Lower_Layer->Transmit(p_Datagram);
}
}
// Receive handler for a datagram received from lower layer
void Handle_Receive(Datagram *p_Datagram)
{
Process_Receive(p_Datagram);
if (m_p_Upper_Layer)
{
m_p_Upper_Layer->Handle_Receive(p_Datagram);
}
}
};
class RLC_Physical_Layer : public Protocol_Layer
{
void Process_Transmit();
void Process_Receive();
public:
RLC_Physical_Layer(Layer *p_Lower_Layer, Layer *p_Upper_Layer)
: Protocol_Layer(p_Lower_Layer, p_Upper_Layer)
{ }
};
class RLC_Datalink_Layer : public Protocol_Layer
{
void Process_Transmit();
void Process_Receive();
public:
RLC_Datalink_Layer(Layer *p_Lower_Layer, Layer *p_Upper_Layer)
: Protocol_Layer(p_Lower_Layer, p_Upper_Layer)
{ }
};
class RLC_Network_Layer : public Protocol_Layer
{
void Process_Transmit();
void Process_Receive();
public:
RLC_Network_Layer(Layer *p_Lower_Layer, Layer *p_Upper_Layer)
: Protocol_Layer(p_Lower_Layer, p_Upper_Layer)
{ }
};
|
Application of the Dependency Inversion Principle has given us the
following advantages:
- There is no coupling between the three layers. Each layer's
implementation does not even need to include the header files for
other layers.
- You have flexibility in mixing and matching different layers.
You could use the physical layer implementation with a different
datalink layer. This can be accomplished without writing a single
line of code in the physical layer class.
- You can sandwich layers between two layers. For example, you can
add a debugging layer between physical and datalink layers. This
layer could trap messages between the two layers.
For more details about the design, please refer to the
Protocol Layer Design Pattern article. |