Unity 从零开始的框架搭建1-4 彻底理解IOC的反转与注入作用[干货]
咩咩
Unity 从零开始的框架搭建1-3 关于命令模式的一些思考-CSDN博客
本人水平有限 如有不足还请斧正,该文章专栏是向QFrameWork作者凉鞋老师学习总结得来,吃水不忘打井人,不胜感激
要说之前听都没听过这个东西 ,到底是什么东西?我试图学会它并分享出来
先说结论,IOC有以下作用:
1. 依赖注入:通过构造函数、属性或方法将依赖对象注入到目标对象中,而不是在目标对象内部创建依赖对象
2. 控制反转:将对象的创建和生命周期管理从应用程序代码中移到容器中,由容器来控制对象的创建和销毁其结构图如下:
0.场景
我先举一个例子,士兵和怪物都继承IWeapon类都可以开火
using UnityEngine;
public class NoIOCExample : MonoBehaviour
{
private Solider0 solider0;
private Enemy0 solider1;
void Start() {
solider0 = new Solider0();
solider0.Fires();
solider1 = new Enemy0();
solider1.Fires();
}
}
public interface IWeapon0 {
void Fires();
}
public class Solider0 : IWeapon0 {
public void Fires() {
Debug.Log($"Soldier0 is Attack");
}
}
public class Enemy0 : IWeapon0 {
public void Fires() {
Debug.Log($"Enemy0 is Attack");
}
}
1.不使用IOC
1.我试图加几个士兵比如Solider1,Solider2,Solider3.......就会发现NoIOCExample类持有的士兵对象越来越多(当然怪物同理)
这显然是一种高度的耦合
2.我想要修改士兵的构造参数,我想它的无参改为有参构造比如:加一个伤害值
public class Solider0 : IWeapon0 {
private int attck;
Solider0(int attack){
this.attck = attack;
}
public void Fires() {
Debug.Log($"Soldier0 is Attack");
}
}
然后所有关于该士兵实例化的地方都会报错:
现在我将用所谓的IOC去解耦
2.IOC设计实例与解析
其本质就是一个字典,如果不想那么泛用的话可以把Type改为其他具体类型
然后其有两个方法:
注册接口,定义了Input 与Output两种泛型,注意output是要继承Input的
解析接口,通过传入的Input把Output转为Input(因为output是要继承Input的)
关于 return (TInput)Activator.CreateInstance(implementationType);
就是创建TOutput一个对象然后强转为TInput
using System;
using System.Collections.Generic;
public class IOC {
private Dictionary<Type, Type> iocDict = new Dictionary<Type, Type>();
/// <summary>
/// 注册接口和实现类
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <typeparam name="TOutput"></typeparam>
public void Register<TInput, TOutput>() where TOutput : TInput {
if(iocDict.ContainsKey(typeof(TInput)))
iocDict[typeof(TInput)] = typeof(TOutput);
else
iocDict.Add(typeof(TInput), typeof(TOutput));
}
/// <summary>
/// 解析接口
/// </summary>
/// <typeparam name="TInterface"></typeparam>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public TInput Resolve<TInput>() {
var key = typeof(TInput);
if (iocDict.TryGetValue(key, out var implementationType)) {
return (TInput)Activator.CreateInstance(implementationType);
}
else {
throw new Exception("未注册该接口"+implementationType);
}
}
}
3.IOC作用体现
控制反转(Inversion of Control)
这个反转的意思是内部变成了外部
原来:NoIOCExample(内部)
对Solider0
和Enemy0
的创建具有完全的控制权
反转:IOCExample
只通过IOC
容器的Resolve
方法来获取已经创建好的IWeapon
实例 ,控制权从使用依赖的对象(IOCExample
)转移到了外部容器(IOC
)
如果加许多士兵也不会让IOCExample 类持有大量的士兵对象了,而是直接让一个container疯狂去注册即可
using UnityEngine;
public class IOCExample : MonoBehaviour
{
private IOC container;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
container =new IOC();
container.Register<IWeapon, Solider>();
IWeapon weapon = container.Resolve<IWeapon>() ;
weapon.Fire();
}
}
public interface IWeapon {
void Fire();
}
public class Solider :IWeapon {
private int attck;
public Solider(int attack) {
this.attck = attack;
}
public void Fire() {
Debug.Log($"Soldier0 is Attack");
}
}
依赖注入(Dependency Injection)
在IOCExample
类中,IOCExample
依赖IWeapon
接口
IOC
容器通过Register
方法注册了IWeapon
与Solider
的映射关系,并在Resolve
方法中创建Solider
实例(IWeapon
的具体实现),然后将这个实例 “注入” 给IOCExample
类(通过IOCExample
调用Resolve
方法获取),这里IOC
容器扮演了注入者的角色,将IWeapon
的实例注入到需要它的IOCExample
类中,从而实现了依赖注入
现在我给士兵类加构造函数也不会报错,至于在哪里去给到这个attack,需要去修改IOC类,这里我给出一种解法
using System;
using System.Collections.Generic;
public class IOC
{
private Dictionary<Type, Type> iocDict = new Dictionary<Type, Type>();
private Dictionary<Type, object[]> constructorArgsDict = new Dictionary<Type, object[]>();
/// <summary>
/// 注册接口和实现类,无参数
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <typeparam name="TOutput"></typeparam>
public void Register<TInput, TOutput>() where TOutput : TInput
{
if (iocDict.ContainsKey(typeof(TInput)))
iocDict[typeof(TInput)] = typeof(TOutput);
else
iocDict.Add(typeof(TInput), typeof(TOutput));
}
/// <summary>
/// 注册接口和实现类,带构造函数参数
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <typeparam name="TOutput"></typeparam>
/// <param name="args">构造函数参数</param>
public void Register<TInput, TOutput>(params object[] args) where TOutput : TInput
{
if (iocDict.ContainsKey(typeof(TInput)))
iocDict[typeof(TInput)] = typeof(TOutput);
else
iocDict.Add(typeof(TInput), typeof(TOutput));
constructorArgsDict[typeof(TInput)] = args;
}
/// <summary>
/// 解析接口
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public TInput Resolve<TInput>()
{
var key = typeof(TInput);
if (iocDict.TryGetValue(key, out var implementationType))
{
if (constructorArgsDict.TryGetValue(key, out var args))
{
return (TInput)Activator.CreateInstance(implementationType, args);
}
else
{
return (TInput)Activator.CreateInstance(implementationType);
}
}
else
{
throw new Exception("未注册该接口" + implementationType);
}
}
}
4.你是否看出了最为关键的点?
就是这行代码: return (TInput)Activator.CreateInstance(implementationType);而其前提条件就是TOutput继承了TInput,这就是控制反转的核心
依赖注入的核心就是Resolve方法的返回值,其将需要的类转为TInput的实例注入到了任意类之中
更多推荐
所有评论(0)