StrangeIOC

思考并回答以下问题:

  • command必有对service或model的引用。command必有的Execute里面执行的是service的方法。command里面没有维护dispatcher,service里面维护。怎么理解?
  • 让容器去决定依赖关系。怎么理解?

IoC

控制反转即IoC(Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器

IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:

  • 1.依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件。
  • 2.依赖注入(Dependency Injection):组件不做定位查询,只提供普通的方法让容器去决定依赖关系。后者是时下最流行的IoC类型,其又有接口注入(Interface Injection),设值注入(Setter Injection)和构造函数注入(Constructor Injection)三种方式。

依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露setter方法或者带参数的构造函数或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:

  • 1.查找定位操作与应用代码完全无关。
  • 2.不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
  • 3.不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。

依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。

意思是自身对象中的内置对象是通过注入的方式进行创建。依赖注入有两种实现方式:Setter方式(传值方式)和构造器方式(引用方式)。

容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(setter)或者是构造子传递给需要的对象。

相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。

为什么要用IOC?

第一:对象的实例化不是一件简单的事情,比如对象的关系比较复杂,依赖关系往往需要程序员去维护,这是一件非常头疼的事。

第二:解耦,由容器去维护具体的对象。

第三:托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分过程交给容器,应用程序则无需去关心类是如何完成代理的。

控制反转(Inverse of Control)

控制反转即IoC(Inversion of Control),从字面上理解就是控制反转,将对在自身对象中的一个内置对象的控制权反转。所谓的反转,即把内置对象的控制权反转给一个容器,而应用程序只需要提供对象的类型即可。

这是一种解耦的设计思想,并不是什么具体的技术。基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。实现IOC的技术手段:DI(依赖注入)和DL(依赖查找)。

图1:软件系统中耦合的对象

图2:IOC解耦过程

拿掉IOC容器后的各个对象

控制反转(IoC)到底为什么要起这么个名字?我们来对比一下:

软件系统在没有引入IoC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

软件系统在引入IOC容器之后,这种情形就完全改变了,如图2所示,由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IoC容器会主动创建一个对象B注入到对象A需要的地方。

通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

IOC的别名:依赖注入(DI)

2004年,Martin Fowler探讨了同一个问题,既然IoC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IoC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦。

技术剖析

IoC中最基本的技术就是“反射(Reflection)”编程。反射通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。反射的应用是很广泛的。

反射技术其实很早就出现了,但一直被忽略,没有被进一步的利用。当时的反射编程方式相对于正常的对象生成方式要慢至少得10倍。现在的反射技术经过改良优化,已经非常成熟,反射方式生成对象和通常对象生成方式,速度已经相差不大了,大约为1-2倍的差距。

我们可以把IoC容器的工作模式看做是工厂模式的升华,可以把IoC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IoC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

使用IoC框架应该注意什么

使用IoC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IoC框架的缺点,做到心中有数,杜绝滥用框架。

  • 第一、软件系统中由于引入了第三方IoC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IoC框架的时候,会感觉系统变得不太直观。

  • 第二、由于IoC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。

  • 第三、具体到IoC框架产品来讲,需要进行大量的配置工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。

StrangeIOC

S是Service,负责与服务器等交互取得数据。
Controller是核心层。是命令模式的实现。
View只负责UI的显示,提供数据更新的接口给Mediator,Mediator与外界(Command、Model、Service)交互。

代码

游戏中出现一个随机运动的Cube,有两个功能:

  • 最开始从Service获取一个分数并显示。
  • 点击到Cube会把当前分数+1,然后上传到Service进行存储。

Demo1ContextView.cs 挂载到空物体ContextView,继承View。

1
2
3
4
5
6
7
8
9
10
11
12
13
using strange.extensions.context.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Demo1ContextView : ContextView
{
private void Awake()
{
// 启动StrangeIoc框架
this.context = new Demo1Context(this);
}
}

View

CubeView.cs 只负责UI的显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using strange.extensions.mediation.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CubeView : View
{
// View里面也有dispatch,用来发送消息
[Inject]
public IEventDispatcher dispatcher { get; set; }

private Text scoreText;

// 慎用Start和Awake。不能覆盖掉父类的方法
public void Init()
{
scoreText = GetComponentInChildren<Text>();
}

void Update()
{
transform.Translate(new Vector3(Random.Range(-1, 2), Random.Range(-1, 2), Random.Range(-1, 2))* 0.2f);
}

// 鼠标点到此Cube时执行
private void OnMouseDown()
{
// 发送事件
// 谁想监听就AddLister然后在IoC容器绑定
dispatcher.Dispatch(Demo1MediatorEvent.ClickDown);
}

// 给Mediator提供的更改分数Text的方法
public void UpdateScore(int score)
{
scoreText.text = score.ToString();
}
}

Mediator里面主要是两块功能,其中一个是改变数字,另外一个是随着点击更新数字。

所以首先这个类先监听了关于改变分数的类,然后向Controller里面的RequestCommand类中请求分数。

而这个类,根据cubeView中的dispatcher监听OnClickDown的点击事件(点击事件里也是请求更新分数)。

Demo1MediatorEvent.cs View监听枚举

1
2
3
4
5
6
7
8
using UnityEngine;
using System.Collections;

public enum Demo1MediatorEvent
{
Enum_Mediator_ScoreChange,
Enum_Mediator_ClickDown
}

CubeMediator.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using strange.extensions.context.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CubeMediator : Mediator
{
// 对CubeView的引用,在IoC容器指定注入
[Inject]
public CubeView cubeView { get; set; }

// 通过dispatcher与外界沟通
// 全局的dispatcher
[Inject(ContextKeys.CONTEXT_DISPATCHER)]
public IEventDispatcher dispatcher { get; set; }

// StrangeIOC提供的,一旦绑定View后就执行的方法
public override void OnRegister()
{
Debug.Log(cubeView);

cubeView.Init();
//
dispatcher.AddListener(Demo1MediatorEvent.ScoreChange, OnScoreChange);
cubeView.dispatcher.AddListener(Demo1MediatorEvent.ClickDown, OnClickDown);

// 通过dispatcher发起请求分数的命令
dispatcher.Dispatch(Demo1CommandEvent.RequestScore);
}

// 解绑View后执行
public override void OnRemove()
{
dispatcher.RemoveListener(Demo1MediatorEvent.ScoreChange,OnScoreChange);
dispatcher.RemoveListener(Demo1MediatorEvent.ClickDown, OnClickDown);
}

public void OnScoreChange(IEvent evt)
{
// cubeView.UpdateScore(scoreModel.Score);
cubeView.UpdateScore((int)evt.data);

}

public void OnClickDown()
{
dispatcher.Dispatch(Demo1CommandEvent.UpdateScore);
Debug.Log("OnClickDown");
}
}

Model

ScoreModel.cs 数据实体类,不提供CURD方法

1
2
3
4
5
6
7
8
9
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 不需要继承任何类
public class ScoreModel
{
public int Score { get; set; }
}

Service

Demo1ServiceEvent.cs

1
2
3
4
public enum Demo1ServiceEvent  
{
Enum_Service_ReceiveScore
}

IScoreService.cs Service接口

1
2
3
4
5
6
7
8
9
10
11
12
13
using strange.extensions.dispatcher.eventdispatcher.api;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IScoreService
{
void RequestScore(string url); // 请求分数
void OnReceiveScore(); // 收到服务器端发送过来的分数
void UpdateScore(string url, int score);

IEventDispatcher dispatcher { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System.Collections;
using System.Collections.Generic;
using strange.extensions.dispatcher.eventdispatcher.api;
using UnityEngine;

public class ScoreService : IScoreService
{
[Inject]
public IEventDispatcher dispatcher{ get; set; }

// 给外界调用的请求分数的方法
public void RequestScore(string url)
{
Debug.Log("Request score from url:" + url);

OnReceiveScore();
}

// 内部函数,收到服务器发过来的初始分数
private void OnReceiveScore()
{
int score = Random.Range(0, 100);
// 发送消息
dispatcher.Dispatch(Demo1ServiceEvent.ReceiveScore, score);
}

// 更新分数
public void UpdateScore(string url, int score)
{
Debug.Log("Update score to url:" + url + " new score:" + score );
}
}

Command

Demo1CommandEvent.cs 事件枚举类

1
2
3
4
5
public enum Demo1CommandEvent  
{
Enum_Cmd_RequestScore,
Enum_Cmd_UpdateScore
}

StartCommand.cs 定制框架启动时要触发的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using strange.extensions.command.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 开始命令
public class StartCommand : Command
{
/// <summary>
/// 当这个命令被执行的时候,默认会调用Execute方法
/// </summary>
public override void Execute()
{
Debug.Log("Start Command Execute");
}
}

RequestScoreCommand.cs 请求分数类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using strange.extensions.command.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RequestScoreCommand : Command
{
[Inject]
public IScoreService scoreService { get; set; }
[Inject]
public ScoreModel scoreModel { get; set; }

public override void Execute()
{
// 让这个对象不销毁
Retain();
scoreService.dispatcher.AddListener(Demo1ServiceEvent.RequestScore, OnComplete);
Debug.Log("requestscoreCommand");
scoreService.RequestScore("http://xx/xxx/xxx");
}

public void OnComplete(IEvent evt) // IEvent存储的就是参数
{
Debug.Log("requestScore OnComplete" + evt.data);

scoreService.dispatcher.RemoveListener(Demo1ServiceEvent.RequestScore, OnComplete);

scoreModel.Score = (int)evt.data;
dispatcher.Dispatch(Demo1MediatorEvent.ScoreChange, evt.data);
Release();
}
}

UpdateScoreCommand.cs 更新分数类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using System.Collections;
using strange.extensions.command.impl;

public class UpdateScoreCommand : EventCommand
{
// 维护对service和model的引用,由IoC容器负责注入
[Inject]
public ScoreModel scoreModel { get; set; }
[Inject]
public IScoreService scoreService { get; set; }

public override void Execute()
{
scoreModel.Score++;
scoreService.UpdateScore("http://xxx/xxx", scoreModel.Score);
dispatcher.Dispatch(Demo1MediatorEvent.ScoreChange, scoreModel.Score);
}
}

IoC容器

Demo1Context.cs IoC容器,在此进行绑定和注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using strange.extensions.context.api;
using strange.extensions.context.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Demo1Context : MVCSContext
{
public Demo1Context(MonoBehaviour view) : base(view){}

// 进行绑定映射
protected override void mapBindings()
{
// model
injectionBinder.Bind<ScoreModel>().To<ScoreModel>().ToSingleton();

// service
injectionBinder.Bind<IScoreService>().To<ScoreService>().ToSingleton(); // 只在整个工程中生成一个

// command
// 请求分数
commandBinder.Bind(Demo1CommandEvent.RequestScore).To<RequestScoreCommand>();
// 更新分数
commandBinder.Bind(Demo1CommandEvent.UpdateScore).To<UpdateScoreCommand>();

// mediator
// 完成view和mediator的绑定
mediationBinder.Bind<CubeView>().To<CubeMediator>();

// 绑定开始事件,框架启动后就执行
// ContextEvent.START是框架提供的内置事件枚举
// Once只执行一次
commandBinder.Bind(ContextEvent.START).To<StartCommand>().Once();
}
}

时序

游戏一启动后,执行CubeMediator.cs的OnRegister()方法,调用CubeView.cs的Init()方法,注册监听Service那边分数改变和View的点击事件(中介View和外部),发起请求分数的命令,通过IoC容器的“ commandBinder.Bind(Demo1CommandEvent.RequestScore).To();”执行RequestScoreCommand的Execute()方法

0%