I am trying to learn about DI, to have a better understanding of IoC, and the other benefits.
Pre DI, I have a project that has a UI project (MVC), a BusinessLogic project and a DataAccess project. I also have a SharedLib project. All projects have a reference to SharedLib. UI has a reference to BusinessLogic, and BusinessLogic has a reference to DataAccess.
I want to add the Interfaces now. So I go to my DataAccess, and add an Interface for each class, and populat them with their methods. I do the same for the business logic layer.
But in order to inject the DataAccess class, which I instantiate in the BusinessLogic class in the UI project, I need a reference to my Data project, because the UI project (correctly, I think) has no idea what an 'IDataAccess' interface is. The only fix I can see is to add a project reference in my UI to my DA project - which seems wrong.
And if I try add Unity as my container (One day in the future, once i work out how that all works), and want to initialise my Interface/Class relationships in the UI project - same issue.
Maybe the interfaces must go in some shared project? Or one project up? How should this be handled?
If you don't want the references between projects you could look into factories/abstract factories.
Your UI knows about your business layer, so you want to define a factory in your business layer which knows how to use the data layer. Then you handle all your DI in your composition root (the UI project in this example).
A simple example below using a console app as the UI, sticking to the references you stated in your question
Data layer
public interface IDataAccess
{
string GetData();
}
public class XmlDataAccess : IDataAccess
{
public string GetData()
{
return "some data";
}
}
Business layer
public interface IDataAccessFactory
{
IDataAccess GetDataAccess();
}
public class XmlDataAccessFactory : IDataAccessFactory
{
public IDataAccess GetDataAccess()
{
return new XmlDataAccess();
}
}
public class BusinessLogic
{
IDataAccessFactory dataAccessFactory;
public BusinessLogic(IDataAccessFactory dataAccessFactory)
{
this.dataAccessFactory = dataAccessFactory;
}
public void DoSomethingWithData()
{
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
Console.WriteLine(dataAccess.GetData());
}
public string GetSomeData()
{
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
return dataAccess.GetData();
}
}
UI
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IDataAccessFactory, XmlDataAccessFactory>();
var logic = container.Resolve<BusinessLogic>();
logic.DoSomethingWithData();
string useDataInUI = logic.GetSomeData();
Console.WriteLine("UI " + useDataInUI);
Console.ReadKey();
}
It's a contrived example so it looks like abstraction for nothing, but with a real world example it would make more sense.
e.g. you might have a bunch of different data access classes in your data layer database, xml files, etc. so you might define a factory for each in your business layer.
The factory could contain a lot more logic about the nitty gritty of the data layer, or as an abstract factory provide a set of individual factories to the business logic layer.
Business layer
You might instead have an abstract factory in the business layer such as
public interface IPlatformFactory
{
IDataAccessFactory GetDataAccessFactory();
IPricingFactory GetPricingFactory(); // might be in the business project, or another project referenced by it
}
with a concrete factory
public class WebPlatformFactory : IPlatformFactory
{
IDataAccessFactory GetDataAccessFactory()
{
return new XmlDataAccessFactory();
}
IPricingFactory GetPricingFactory()
{
return new WebPricingFactory(); // not shown in the example
}
}
(You might have additional concrete factories such as RetailPlatformFactory
, etc.)
Your BusinessLogic
class would now look something like
public class BusinessLogic
{
IPlatformFactory platformFactory;
public BusinessLogic(IPlatformFactory platformFactory)
{
this.platformFactory = platformFactory;
}
public void DoSomethingWithData()
{
IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
Console.WriteLine(dataAccess.GetData());
}
public string GetSomeData()
{
IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
return dataAccess.GetData();
}
}
Data layer
Your business layer no longer needs to provide an IDataAccessFactory
to your UI so you can move it into your data layer in this example. So the data layer classes would be
public interface IDataAccess
{
string GetData();
}
public class XmlDataAccess : IDataAccess
{
public string GetData()
{
return "some data";
}
}
public interface IDataAccessFactory
{
IDataAccess GetDataAccess();
}
public class XmlDataAccessFactory : IDataAccessFactory
{
public IDataAccess GetDataAccess()
{
return new XmlDataAccess();
}
}
UI
Now you'd in the UI you'd configure the container and perform similar actions as
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IPlatformFactory, WebPlatformFactory>();
var logic = container.Resolve<BusinessLogic>();
logic.DoSomethingWithData();
string useDataInUI = logic.GetSomeData();
Console.WriteLine("UI " + useDataInUI);
Console.ReadKey();
}
The UI then knows nothing about the data layer/access, it's just handing off the factory creation to the business layer, which holds the data (and pricing) references.
Some recommended reading:
Composition Root
Implementing an Abstract Factory
Compose object graphs with confidence
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With