Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / C# 7.0

Inversion Of Control IOC with Containers (aka Dependency injection)

Rate me:
Please Sign up or sign in to vote.
3.45/5 (6 votes)
20 Apr 2020CPOL7 min read 6K   43   8   1
Concept and implementation of Inversion Of Control, aka IOC through Dependency Injection (DI)
Inversion Of Control is a very powerful pattern as part and/or on top of the strategy pattern for loose coupling. Not only do you separate implementation from the interface (Strategy pattern), but you also invert the control of what the implementation should be. The control is no longer part of the "using" object, but control of implementation is inverted to an extern system (mostly, object containers, initialized by a using program). The container injects the implementation dependencies into all using objects that need an implementation for their required interfaces.

Sample Image - maximum width is 600 pixels

Using IOC (Dependency Injection) and Strategy pattern to loosely couple your implementation objects in your sub-systems ((micro)-services).

Introduction

This article provides the concept and implementation of Inversion Of Control, aka IOC through Dependency Injection (DI).

It is used in combination with the Strategy pattern with interfaces and implementation classes. After that, examples follow in Java (using Spring Boot) and .NET C# (using Autofac).

As explained in short, IOC (DI) on top of the Strategy pattern allows OO classes to be very loosely coupled inside the implementation of a component. That allows for much easier refactoring of code, better re-use of classes, and (if properly designed), better separation of concern, and easier to adjust components to new or changed functionality (flexibility and ease of adoption to changes). So, although it is not a pattern to loosely couple components together, it certainly can help in re-using (e.g., binary) libraries created with these patterns in other components or apps.

See related articles:

How is this article built up?

  • Examples in Java and C# of final usage of Strategy and IOC (through Dependency Injection)
  • Concept of Strategy pattern
  • Concepts of IOC with DI (IOC, Field and CTOR injection)
  • Java implementation details Spring Boot
  • C# implementation details with AutoFac
  • Wrap-up and conclusions, and more

Before Reading This Article

I will show the Strategy pattern in Java and C#. IOC(Dependency Injection) on top of Strategy in Java is demonstrated with the Spring Boot ApplicationContext container and @Autowiring.
IOC(Dependency Injection) on top of Strategy in C# is demonstrated using Autofac.

Note: The purist under us of course will say, "DI is not the same IOC", it is "an implementation" of IOC. Duly noted. See also [2].

Strategy Pattern Final Usage Example in Java (1+)

Image 2

Java
//
//An example of the usage
//
Contract contract = new ContractImpl("cloe");
//
//or
//
Contract contract = new ContractImpl();
contract.getPerson().setName("roland");

Strategy Pattern Final Usage Example in C# (4+)

Sample Image - maximum width is 600 pixels

C#
//
//An example of the usage in C#
IContract contract = new Contract("cloe");
//
//or
//
IContract contract = new Contract();
contract.Person().Name = "roland";

IOC Pattern Final Usage Example in Java (1+)

Sample Image - maximum width is 600 pixels

Java
//
//An example of the usage in Java
//
@Autowired
public Contract contract;
public void run(String... args) {
	contract.getPerson().setName("roland");
}

IOC Pattern Final Usage Example in C# (4+)

Image 5

C#
//
//An example of the usage in C#
//
IContract contract = container.Resolve<IContract>();
contract.Person().Name = "roland";

Concept of Strategy Pattern

The idea of this pattern is a basic OO idea: separate class implementation from its interface. So, all "public" behaviour is exposed through the interface of the class.

In this article, I choose for the "clean" implementation, and separated the interfaces from the classes in separate libraries.

I always do advice this kind of implementation. This is not in the scope of this article to go into detail on this aspect. But trust me, it is a little overhead, but in bigger systems, you will end up in complex build dependencies if you don't.

Interfaces, Interfaces, Interfaces...

In the basics, there are these steps:

  • Define your public methods in an interface
  • Implement the interface in a class
  • Define your associations and/or aggregations on interface level instead of on an implementation level

Pros of Strategy Pattern

There are a real large number of important pros on Strategy, especially, in bigger systems:

  • Build dependencies are much easier to solve: First build the component with all the interfaces, then the implementing components
  • Testability in Unit tests is much easier. You can mock your interfaces more easily then your implementations
  • Separation of interface and implementation allows for multiple implementations
  • Dependency injection is much easier to implement
  • If you do, Aspect-oriented programming and Interceptors using Proxies (See [3]) is possible. If you don't, it isn't.

Cons of Strategy Pattern

  • You need to define your public methods and properties twice (one in class, one in interface) which makes refactoring a bit more work
  • You may find code harder to read. After all, what is the implementation going to be behind the interface?

Note: There is almost no difference in Java and C# in the code implementing the Strategy pattern. Conceptually, 100% the same. The small differences are:

  • C# has as coding standard default interface I... (IContract, Contract)
  • Java has as coding standard default class ...Impl (Contract, ContractImpl)
  • C# has {get;set;} syntax (String Name {get;set;})
  • Java has Type get...() and set..(Type value) syntax (String getName() void setName(String name)).

Strategy Pattern Implementation in Java (1+)

Image 6

Java
//
// Implementation and Interface separation: Strategy pattern example
//
//
// The Person interface
//
public interface Person {
	String getName();
	void setName(String name);
}

//
// The Person implementation
//
public class PersonImpl implements Person {
	private String name;
	public String getName(){
		return name;
	}
	public void setName(String name){
		this.name = name;
	}
}

//
// The Contract interface
//
public interface Contract {
	Person getPerson();
	void setPerson(Person person);
}

//
// The Contract implementation
//
public class ContractImpl implements Contract{
	
	private Person person = new PersonImpl();
	
	public ContractImpl(){
	}
	
	public ContractImpl(String personName){
		this.person.setName(personName);
	}
	
	public Person getPerson(){
		return person;
	}
        
        public void setPerson(Person person){
		this.person = person;
	}
}
...
//An example of the usage
Contract contract = new ContractImpl("cloe");
//or
Contract contract = new ContractImpl();
contract.getPerson().setName("roland");

Strategy Pattern Implementation Example in C# (1+)

Image 7

Java
//
// The Person interface
//
public interface IPerson 
{
	String Name {get; set;}
}

//
// The Person implementation
//
public class Person : IPerson 
{
	public String Name {get; set;}	
}

//
// The Contract interface
//
public interface IContract 
{
	IPerson Person {get; set;}	
}

//
// The Contract implementation
//
public class Contract : IContract
{
	public Contract(){}
	public Contract(String personName)
	{
		this.person.Name = personName;
	}
	public IPerson Person {get; set;} = new Person();	
}
...
...
//An example of the usage
IContract contract = new Contract("cloe");
//or
IContract contract = new Contract();
contract.Person.Name = "roland";

IOC Pattern Example in Java (1.7+, Spring Boot)

Image 8

Now, only the implementations change, not the interfaces:

Java
//
// The Person implementation: @Component lets the PersonImpl being found for a @ComponentScan,
// to register the interface to the Container Spring boot context
//
@Component
public class PersonImpl implements Person {

	private String name;
	public String getName(){
		return name;
	}
	public void setName(String name){
		this.name = name;
	}
}

//
// The Contract implementation
//
@Component
public class ContractImpl extends Contract{
	
	@Autowired
	private Person person;

	public Person getPerson(){
		return person;
	}
}
...
//An example of the usage
@ComponentScan()
@SpringBootApplication()
public class IOCApplication {..}
...
public void run(String... args) {
	//
	//note the type Contract being a interface Contract.class type!
	//
    Contract contract = context.getBean(Contract.class);
	contract.getPerson().setName("roland");
}

//or, and note the type Contract being a interface type!
...
@Autowired
public Contract contract;
public void run(String... args) {
	contract.getPerson().setName("roland");
}

IOC Pattern Example in C# (C# 7+, Autofac)

Image 9

Now, the code in C# has to change on one small point, and we have to do some object registration.

Note 1: This is an example of auto Field injection, supported by Autofac by ContainerBuilder.PropertiesAutowired();

Note 2: In the download code, there are two implementations, one for strategy, and one for IOC DI, and also a testproject for each.

Java
public IPerson Person { get; set; } //= new Person(); => Injected by Container
	
ContainerBuilder containerBuilder = new ContainerBuilder();
//
// Register Person and Contract on its interfaces
//
containerBuilder
	.RegisterType<Contract>()
	.As<IContract>()
	.PropertiesAutowired();
containerBuilder
	.RegisterType<Person>()
	.As<IPerson>()
	.PropertiesAutowired();
IContainer container = containerBuilder.Build();
using (var scope = container.BeginLifetimeScope())
{
	//
	//An example of IContract the usage
	//
	IContract contract = scope.Resolve<IContract>();
	//
	// Check that container injected the Person
	//
	Assert.IsNotNull(contract.Person);
	contract.Person.Name = "cloe";
	Assert.AreEqual("cloe", contract.Person.Name);
}

Now, whereas Autofield injection is OK with me, because I'm a pragmatic guy, there are purists that favour Constructor injection. And, there are indeed situations where we need that or it makes the world a little cleaner. I advice to use CTOR injection always, but choose your pick and learn to live with it.

Pros are:

  • CTOR DI code directly reveals all dependencies
  • CTOR DI can by used to do extra initialization

Fine with me, here it is:

Java
...
//
// CTOR Dependency injection. 
// So, the container "sees" a IPerson is needed during factoring, and factors that. 
//
public Contract(IPerson person)
{
	this.Person = person;
}
public Contract(IPerson person, String name)
{
	this.Person = person;
	person.Name = name;
}
...
//
// Note the missing .PropertiesAutowired(); in the registration. 
// Injection of IPerson now on CTOR.
//
ContainerBuilder containerBuilder = new ContainerBuilder();
containerBuilder
	.RegisterType<Contract>()
	.As<IContract>();
containerBuilder
	.RegisterType<Person>()
	.As<IPerson>();
...rest code same...
//but this uses the person/name ctor. Person is factored and injected by the container.
List<Parameter> parameters = new List<Parameter>();
parameters.Add(new NamedParameter("name", "cloe"));
contract = scope.Resolve<IContract>(parameters);

That concludes Strategy and IOC DI with Autofac in C#, with both Field and CTOR injection.

Note 1: Autofac has registration Module concepts. Please use them. See code attached in my other articles [3] and [4]

Conclusion

What Did We Learn in this Article?

  • Strategy pattern is possible in C# and Java, almost 100% same.
  • IOC DI is possible in C# and Java, both with CTOR and Field injection.
  • We saw that it is now quite simple to add two implementations with the same interfaces (see attached code).

Points of Interest

Modern applications, whether or not apps, micro-services or libraries, almost all use containers and IOC. I did not go into lengthy features and concepts of containers, like Scope and LifeTime, and a zillion other features. There are examples enough on the Internet. As well as other containers than autofac (Pico, Castle Windsor, ...), I also did not go into detail on testing pros of the Strategy pattern, but I hope to write an article on layered unit testing as well, showing that.

A word of advice on structuring your code properly with a few tips:
If you find that you have more than 2 or 3 dependencies in your CTOR, you probably need to refactor your code. There is a big chance that your class has more than 1 responsibility. Split your class then, and let the caller use two classes that work together. Another solution is to use a Facade pattern, where you factor code to an extra class, with a subset of the dependencies, and include that class in your CTOR. You build some hierarchical "pipeline" then. That is fine. Either way: keep your dependencies [2-4] per class or so max.

I do want to note that I guess I started making code with IOC DI containers about 7 years ago. It changed my coding world completely. And, it takes a while (2 months or so) to get used to it. But, since I switched, I dare say, I can write real OO-code now. Not that I do it all the time :-), but I can now... What I did before was nothing of the kind, actually.

Then, a word on writing big systems or components with IOC and DI. I find IOC DI code quite easy to read per class, but very hard to follow the code throughout a component. Almost, impossible. And, that is good news!. Because the solution is simple: Document your components with UML class diagrams and sequence diagrams and the lot! UML gives all the possibilities to reveal that structure and flow of code. Use the power please?

And, if you hate UML drawing tools (I do), another tip: Start using PlantUML! See the UML code of this article attached as well. Don't let it be an excuse for not writing UML of your code. That is just plain unprofessional or lazy or both.

History

  • 19th April, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Roos ICT Architectures
Netherlands Netherlands
Roland is an experienced, hands-on architect on modern (micro-)services orientation.
All modern OO-oriented platforms, C# DotNet, Java Spring Boot, Python and C++ have his interest. He is experienced in all those platforms.
Architectures, patterns, concepts and frameworks are more important than tooling and languages, after all. You should apply a tool, platform or language in a modern micro-service, because it fits best to the problem at hand. Not because you're most familiar with itSmile | :) .

Comments and Discussions

 
PraiseGood article Pin
Member 1144773225-Aug-20 22:33
Member 1144773225-Aug-20 22:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.