public class MyDiContainer : DiContainer
{
public override void Provide()
{
}
}
In this class you will provide every class which you want to be injected.
For example, let's create class for testing ->
public class SimpleClass
{
public void Print()
{
Debug.Log("Hello from simple class!");
}
}
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddFromInstance(new SimpleClass());
}
}
AddFromInstance
is just one of posible solution to provide wanted class into DI (more options will be described latter)
public class GameManager : MonoBehaviour
{
[Inject]
public SimpleClass simpleClass;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
simpleClass.Print();
}
}
Inject
attribute tell DI to provide class and DiContainer.Instance.Bind(this);
tells DI container to instantete all required things from DI.
After that you can access to the object of injected class, like simpleClass.Print();
.
Last step is to add empty game object into the sceen and to attach your container implementation (in this case MyDiContainer
) to it.
Like you see abowe, if you add iy by instance, all other classes which access to the SimpleClass injeceted by instance, will actually access to the same object.
If you add one more inject to the GameManager required SimpleClass, they will receive the same object:
public class GameManager : MonoBehaviour
{
[Inject]
public SimpleClass simpleClass;
[Inject]
public SimpleClass simpleClass2;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
bool isSame = simpleClass.GetHashCode() == simpleClass2.GetHashCode(); // true
}
}
To have different object, you must use classification by name:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddFromInstance(new SimpleClass(), "simple1"); // this will go into simple1 class
AddFromInstance(new SimpleClass(), "simple2"); // this will go into simple2 class
}
}
And than to tell your class which to use:
public class GameManager : MonoBehaviour
{
[Inject("simple1")] // ADD HERE FOR simple1
public SimpleClass simpleClass;
[Inject("simple2")] // ADD HERE FOR simple2
public SimpleClass simpleClass2;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
bool isSame = simpleClass.GetHashCode() == simpleClass2.GetHashCode(); // false
}
}
Sometims you want everytime new instance when you required it by inject. In that case you must to use injection by type and tell DI to create every time new instance:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddByType<SimpleClass>(InjectionType.AsFactory);
}
}
AddByType
will tell DI to create that type of class by himself and InjectionType.AsFactory
will tell to create every time new instace.
Example:
public class GameManager : MonoBehaviour
{
[Inject]
public SimpleClass simpleClass;
[Inject]
public SimpleClass simpleClass2;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
bool isSame = simpleClass.GetHashCode() == simpleClass2.GetHashCode(); // false
}
}
If we have interaface
public interface ISimpleClass
{
void Print();
}
And change simple class to implement that interface:
public class SimpleClass : ISimpleClass
{
public void Print()
{
Debug.Log("Hello from simple class!");
}
}
You must to say DI which class to instantiate for that interface:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddByType<ISimpleClass,SimpleClass>(InjectionType.AsFactory);
}
}
Definition for AddByType is AddByType<For all required interface, Provde class>
And you can use it:
public class GameManager : MonoBehaviour
{
[Inject]
public ISimpleClass simpleClass; // define interface usage
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
simpleClass.Print();
}
}
Let create one more extension for ISimpleClass
:
public class NotSoSimpleClass : ISimpleClass
{
public void Print()
{
Debug.Log("Not so simple class!");
}
}
Now we can separate it by classification:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddByType<ISimpleClass,SimpleClass>(InjectionType.AsFactory, "simple");
AddByType<ISimpleClass, NotSoSimpleClass>(InjectionType.AsFactory, "notSimple");
}
}
And use it:
public class GameManager : MonoBehaviour
{
[Inject("simple")]
public ISimpleClass simpleClass; // This will be SimpleClass
[Inject("notSimple")]
public ISimpleClass simpleClass2; // This will be NotSoSimpleClass
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
simpleClass.Print();
simpleClass2.Print();
}
}
Let create new class which will be used as singleton:
public class SingletonSample
{
public void Hello()
{
Debug.Log("Hello from singleton!");
}
}
To use it like singleton, you can just inject it:
[Inject]
public SingletonSample singleton;
There is option to provide mono classes from instance.
Create one mono class:
public class UiExample : MonoBehaviour
{
public void Hello()
{
Debug.Log("Hello from mono UI");
}
}
Than provide it to the DI container:
public class MyDiContainer : DiContainer
{
[SerializeField] UiExample uiExample; // Access to it from scene
public override void Provide()
{
AddFromInstance(uiExample); // forward it to the DI
}
}
And use it in your class:
public class GameManager : MonoBehaviour
{
[Inject]
public UiExample uiExample;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
uiExample.Hello();
}
}
Contructor injection is very important and in practice very usefull(or say "most wanted"). Use case for contructor injection is when you want to inject class which also requrire some injections. For example let's create few classes and interfaces:
public interface IEngine
{
void startEngine();
}
and implementation of that interface:
public class DieselEngine : IEngine
{
public void startEngine()
{
Debug.Log("Start Diesel engine!");
}
}
Also:
public interface IWheel
{
void Controll();
}
And implementation:
public class TigarWheel : IWheel
{
public void Controll()
{
Debug.Log("Tiger wheels are OK!");
}
}
Let's say we have Car class and that class requreies injection of Iwheel specific implemention and IEngine. For that case we can use constructor injection:
public class Car
{
private IEngine engine;
private IWheel wheel;
[Inject]
public Car(IEngine engine, IWheel wheel) // Constructor injection
{
this.engine = engine;
this.wheel = wheel;
}
public void StartCar()
{
engine.startEngine();
wheel.Controll();
}
}
Now you just need to sat DI which implementation to use and DI will make rest of the job for you:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddByType<IEngine,DieselEngine>(InjectionType.AsFactory);
AddByType<IWheel, TigarWheel>(InjectionType.AsFactory);
AddByType<Car>(InjectionType.AsFactory);
}
}
So now you can inject car:
public class GameManager : MonoBehaviour
{
[Inject]
public Car car;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
car.StartCar();
}
}
Create one more implementation of IWheel and IEnigne
public class WinteraWheel : IWheel
{
public void Controll()
{
Debug.Log("Wintera wheels are OK!");
}
}
And
public class PetrolEngine : IEngine
{
public void startEngine()
{
Debug.Log("Start Petrol engine!");
}
}
Now we have DieselEngine
and PetrolEngine
engines and TigarWheel
and WinteraWheel
wheels.
So if we want to use them all, wee need to say DI how to create it. And because we have multiple implementations, we must classify it by name:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddByType<IEngine, DieselEngine>(InjectionType.AsFactory, "disel");
AddByType<IEngine, PetrolEngine>(InjectionType.AsFactory, "petrol");
AddByType<IWheel, TigarWheel>(InjectionType.AsFactory, "tiger");
AddByType<IWheel, WinteraWheel>(InjectionType.AsFactory, "wintera");
AddByType<Car>(InjectionType.AsFactory);
}
}
And now, we must to say Car which to use:
public class Car
{
private IEngine engine;
private IWheel wheel;
[Inject]
public Car([Param("disel")] IEngine engine, [Param("wintera")] IWheel wheel) // Classification with params
{
this.engine = engine;
this.wheel = wheel;
}
public void StartCar()
{
engine.startEngine();
wheel.Controll();
}
}
Param
is attribute which you can use to specify which implementation to create. You can add Param
for all parameters from contructor or for some of them.
What if we have mutiple implementation of cars which can inject multiple implementations of wheels and engings. Let's create interace for car:
public interface ICar
{
void StartCar();
}
And implement regular Car
with that interface:
public class Car : ICar
{
private IEngine engine;
private IWheel wheel;
[Inject]
public Car([Param("petrol")] IEngine engine, [Param("wintera")] IWheel wheel)
{
this.engine = engine;
this.wheel = wheel;
}
public void StartCar()
{
engine.startEngine();
wheel.Controll();
}
}
And also create one more car implementation:
public class FastCar : ICar
{
private IEngine engine;
private IWheel wheel;
[Inject]
public FastCar([Param("disel")] IEngine engine, [Param("tiger")] IWheel wheel)
{
this.engine = engine;
this.wheel = wheel;
}
public void StartCar()
{
engine.startEngine();
wheel.Controll();
}
}
As you can see, Car
requires petrol
engine and wintera
wheels. And FastCar
requires disel
engine and tiger
wheels.
So now we just need to register this two implementations in DI container:
public class MyDiContainer : DiContainer
{
public override void Provide()
{
AddByType<IEngine, DieselEngine>(InjectionType.AsFactory, "disel");
AddByType<IEngine, PetrolEngine>(InjectionType.AsFactory, "petrol");
AddByType<IWheel, TigarWheel>(InjectionType.AsFactory, "tiger");
AddByType<IWheel, WinteraWheel>(InjectionType.AsFactory, "wintera");
AddByType<ICar, Car>(InjectionType.AsFactory,"regularCar"); // add for regular car
AddByType<ICar, FastCar>(InjectionType.AsFactory, "fastCar"); // add for fast car
}
}
And now use it:
public class GameManager : MonoBehaviour
{
[Inject("regularCar")] // specify for regular
public ICar car;
[Inject("fastCar")] // specify for fast
public ICar car2;
void Start()
{
// Register class for binding!
DiContainer.Instance.Bind(this);
car.StartCar();
car2.StartCar();
}
}