CSC/ECE 517 Fall 2009/wiki3 5 SD

From Expertiza_Wiki
Jump to navigation Jump to search

Dependency inversion principle

Introduction

SOLID is a combination of five important design principles to achieve a good design:
S – Single Responsibility Principle
O – Open Closed Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency Inversion Principle

We are going discuss more in detail about Dependency Inversion Principle(DIP) here.The objects in our software should not get into a hierarchy where some objects are in the top-level ones, and dependent on low-level objects. Changes in low-level objects will then ripple-through to top-level objects which makes the software very fragile for change. It is always required that 'top-level' objects be very stable and not fragile for change, therefore the dependencies need to inverted.The point of dependency inversion is to make reusable software.Instead of two pieces of code relying on each other, they rely on some abstracted interface. Then either pieces can be reused without the other.

In conventional application architecture, lower-level components are designed to be consumed by higher-level components which enable increasingly complex systems to be built. In this composition, higher-level components depend directly upon lower-level components to achieve some task. This dependency upon lower-level components makes applications rigid, fragile and immobile. Goal of Dependency inversion principle is to decouple the high-level components from low-level components.

What is Dependency inversion principle?

Dependency inversion principle states that:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend upon details. Details should depend upon abstractions.

In conventional architecture, higher-level components depend upon lower-level components as depicted in the following diagram:[1]

http://4.bp.blogspot.com/_rO0OuTtGb2Y/SVgyGeeYzwI/AAAAAAAAATM/NPsuTSArKIg/s400/DIP1.png

In this diagram, component A depends upon component B, which in turn depends upon component C. Due to these dependencies, each of the higher-level components is coupled with the lower-level components.

The goal of the Dependency Inversion Principle is to decouple higher-level components from their dependency upon lower-level components. This may be achieved by creating interfaces as part of the higher-level component package which define the component’s external needs. This allows the component to be isolated from any particular implementation of the provided interface, thus increasing the component’s portability. The following diagram illustrates this relationship:[2]

http://4.bp.blogspot.com/_rO0OuTtGb2Y/SVgyGil3HQI/AAAAAAAAATU/lezX76UCH7Q/s400/DIP2.png


In this diagram, component A provides an interface which defines the services it needs. Component B satisfies this dependency by implementing the interface. The same relationship is additionally shown for components B and C. Take special note that the interfaces are packaged together with the higher-level components and are defined in terms of the higher-level component’s needs, not the lower-level component’s behavior. It is this association of the interface with the client component which logically inverts the conventional dependency flow.

DIP Examples

Pictorial Example: Consider the following block diagram. Here the higher level class, Dinner depends upon the lower level classes Turkey and Stuffing.

Now, if we want to make Chicken instead of Turkey for dinner then the Dinner class becomes useless since it cannot be used in any other context other than making Turkey and Stuffing.

Notice that the module containing the high level policy, i.e. the Dinner() module, is dependent upon the low level detailed modules that it controls. (i.e. Turkey() and Stuffing()). If we could find a way to make the Dinner module independent of the details that it controls, then we could reuse it freely.

Consider the modified class diagram shown here:

Here we have a Dinner class which contains an abstract Meat class and an abstract Sauce class. Now we can have a loop within the Dinner class that gets ingredients from its abstract classes. Yet the Dinner class does not depend upon the “Turkey” nor the Stuffing class at all. Thus the dependencies have been inverted. Now we can reuse the “Dinner” class, independently of the “Turkey” and the “Stuffing” class.


Pictorial example along with code

Consider the following block diagram. The higher level module Copy() depends upon lower level modules ReadUI() and WriteScreen().

The code for the above diagram would look like this:

 Void Copy
{
   ReadUI()
   Writescreen()
}

Notice that the two lower modules ReadUI() and WriteScreen() are reusable where as the higher level module Copy() cannot be reused in any other context other than Reading from a UI and Writing to a screen. This is a little inconvinient because there might be some useful logic in Copy() that could have been used to say Write to a file instead to the screen.

By reversing the dependencies as shown below we can reuse the Copy() module:


class Reader
{
  public:
  virtual int Read() = 0;
};
class Writer
{
  public:
  virtual void Write(char) = 0;
};
void Copy(Reader& r, Writer& w)
{
   int c;
   c = r.Read()
   w.Write(c);
}

Code Example:

Below is an example which violates the Dependency Inversion Principle. There is a manager class which is a high level class, and the low level class Worker. A new module has to be added to the application because in the company there are some new specialized workers employed.So, new class SuperWorker was created for this.Manager class is a complex one containing a very complex logic,and it has to be changed, in order to introduce the new SuperWorker.[3]

Below are the disadvantages:
1.Manager class has to be changed(remember it is a complex one and this will involve some time and effort).
2.Some present functionality from the manager class might be affected.
3.Unit testing should be redone.

All these problems will take a lot of time to solve. Now it would be very simple if the application was designed following the Dependency Inversion Principle. That means the design has the manager class , an IWorker interface and the Worker class implementing the IWorker interface. So,to add the SuperWorker class ,all we have to do is implement the IWorker interface for it.

// Dependency Inversion Principle - Bad example
class Worker {
	public void work() {
		// ....working
	}
}

class Manager {
	Worker m_worker;

	public void setWorker(Worker w) {
		m_worker=w;
	}

	public void manage() {
		m_worker.work();
	}
}

class SuperWorker {
	public void work() {
		//.... working much more
	}
}


Below is the code which supports the Dependency Inversion Principle. In this new design a new abstraction layer is added through the IWorker Interface. Now the problems from the above code are solved:


1.Manager class should not be changed.
2.Minimized risk to affect old funtionallity present in Manager class.
3.No need to redone the unit testing for Manager class.

// Dependency Inversion Principle - Good example
interface IWorker {
	public void work();
}

class Worker implements IWorker{
	public void work() {
		// ....working
	}
}

class SuperWorker  implements IWorker{
	public void work() {
		//.... working much more
	}
}

class Manager {
	IWorker m_worker;

	public void setWorker(IWorker w) {
		m_worker=w;
	}

	public void manage() {
		m_worker.work();
	}
}


Voting Booth Example:

This simple example of a voting booth program shows a non-DIP compliant program.The VotingBooth class is directly dependent on VoteRecorder, which has no abstractions and is the implementing class[4].

package org.springbyexample.vote;

public class VotingBooth {

    VoteRecorder voteRecorder = new VoteRecorder();
    
    public void vote(Candidate candidate) {
        voteRecorder.record(candidate);
    }
    
    class VoteRecorder {
        Map hVotes = new HashMap();
        
        public void record(Candidate candidate) {
            int count = 0;
            
            if (!hVotes.containsKey(candidate)){
                hVotes.put(candidate, count);
            } else {
                count = hVotes.get(candidate);
            }
                      count++; 
                        hVotes.put(candidate, count);  
                    }
    }
}
      


A dependency “inverted” version of this code might look a little different. First, is to define our VoteRecorder interface.

package org.springbyexample.vote;

public interface VoteRecorder {

    public void record(Candidate candidate) ;

}
  

Next is the implementing class,the LocalVoteRecorder, which implements the VoteRecorder interface:

package org.springbyexample.vote;

public class LocalVoteRecorder implements VoteRecorder {

    Map hVotes = new HashMap();
    
    public void record(Candidate candidate) {
        int count = 0;
        
        if (!hVotes.containsKey(candidate)){
            hVotes.put(candidate, count);
        } else {
            count = hVotes.get(candidate);
        }
        
        count++;
        
        hVotes.put(candidate, count);
    }
    
}

And the VotingBooth class:

                    
package org.springbyexample.vote;

public class VotingBooth {

    VoteRecorder recorder = null;
    
    public void setVoteRecorder(VoteRecorder recorder) {
        this.recorder = recorder;
    }
    
    public void vote(Candidate candidate) {
        recorder.record(candidate);
    }

}

Now the LocalVoteRecorder class – the implementing class of the VoteRecorder interface -- is completely decoupled from the VotingBooth class. We have removed all hard-coded references to lower level classes.

DIP and Dependency Injection

A practice often associated with the Dependency Inversion Principle is Dependency Injection. Dependency Injection encompasses a set of techniques for assigning the responsibility of provisioning dependencies for a component to an external source. The goal of Dependency Injection is to separate the concerns of how a dependency is obtained from the core concerns of a component.

One dependency injection technique, referred to as constructor injection, defines the dependencies of a component within its constructor which are supplied at the time of the component’s creation. A simple demonstration of this technique can be seen in the following code example where an instance of an Automobile class is obtained which has a dependency upon an Engine class:

public void CreateAutomobile()
{
  var automobile = new Automobile(new Engine());
 }
public class Automobile
{
 Engine _engine;
 public Automobile(Engine engine)
 {
 _engine = engine;
  }
// ...
}
public class Engine
 {
  // ...
}

In this example, an instance of Automobile is created by passing an instance of a newly created Engine to the Automobile class constructor. This decouples how the Engine is created from the core concerns of the Automobile class. Dependency injection is typically accompanied by the use of interfaces to decouple the dependency from its implementation and is generally facilitated through an Inversion of Control framework to construct complex object graphs. The use of interfaces has been omitted here for clarity, but also to help emphasis that dependency injection is concerned with decoupling how dependencies are obtained, not the abstraction of dependencies

DIP and Interfaces

One common misconception about DIP is that it is considered to be the practice of programming to interfaces rather than to implementations. While it is true that programming to interfaces rather than to implementations is generally considered to be good program design, it is important to remember that Dependency Inversion Principle is not concerned about the use of Interfaces. It is concerned with decoupling higher level components from depending on lower level components.

The Separated Interface Pattern, defined in the book Patterns of Enterprise Application Architecture, sets forth an approach for decoupling components from the implementation details of their dependencies. This is accomplished by defining the interface of the dependency in a separate package from its implementation.One illustration given in the book for how this might be achieved places the interface within the client component package as depicted in the following diagram:

http://3.bp.blogspot.com/_rO0OuTtGb2Y/SVgy9FHs8nI/AAAAAAAAAUI/3kdspB71phA/s400/DIP7.png

As one might observe, this diagram bear a striking resemblance to the structures advocated by the Dependency Inversion Principle. This similarity has led some to consider the two to be synonymous, and to a large extent they are. However, while the organization of the components set forth by each is nearly identical, slight nuances exists between the advocacy and description set forth by each approach.First, while both have in view the decoupling of components from the implementation details of their dependencies, the Dependency Inversion Principle achieves this by assigning ownership of the interface to the higher-level component, whereas the Separated Interface pattern achieves this by separating the interface from the implementation,irrespective of any assumed package ownership.The distinction is the level of emphasis placed by the Dependency Inversion Principle on the value in reusing higher-level components over that of lower-level components. While such an approach is facilitated by the Separated Interface pattern, no such value assignments are assumed.

Another nuance is the level of applicability advocated by each approach. The Dependency Inversion Principle declares that higher-level components should never depend upon lower-level components. In contrast, the Separated Interface pattern advocates a more conservative approach, even advising against introducing the complexity of separating interface from implementation prematurely, favoring rather to keep interface and implementation together until a specialized need arises.

Conclusion

The key to the Dependency Inversion Principle is abstraction. With the abstractions, the details of the system have been isolated from each other, buffered by a wall of stable abstractions. Because of this isolation, changes to some of the details can not propagate throughout the system causing unforeseen harm. Also, the isolated details can be reused in various applications because they do not depend on anything outside of their wall of abstractions.

While applying the dependency inversion principle enables higher level components in a system to be reused, it reduces the ability to reuse lower level components. Programmers must keep in mind and weigh in the consequences of applying the dependency inversion principle because it is not always the case that the higher level components have greater need for decoupling.

Index

References

  1. http://www.ctrl-shift-b.com/2008/12/examining-dependency-inversion.html
  2. http://en.wikipedia.org/wiki/Dependency_inversion_principle
  3. http://www.oodesign.com/dependency-inversion-principle.html
  4. http://www.objectmentor.com/resources/articles/dip.pdf
  5. http://www.oodesign.com/dependency-inversion-principle.html
  6. http://www.slideshare.net/bbossola/geecon09-solid
  7. http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/03/31/ptom-the-dependency-inversion-principle.aspx
  8. http://www.globalnerdy.com/2009/07/15/the-solid-principles-explained-with-motivational-posters/
  9. http://www.davidhayden.com/blog/dave/archive/2005/06/10/1226.aspx
  10. http://www.springbyexample.org/examples/core-concepts.html