StrangeIOC MVCS框架介绍及进阶

发表于2015-08-10
评论0 1.5k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

    这一篇教程将带大家熟悉 StrangeIOC框架 并且讲解一些C#的实用的高级技巧 如 "依赖注入(DI)之属性注入" ,"控制反转(IOC)"。老规矩先问几个问题
    1.框架在项目中实现"看起来"过于复杂?
    答: 原本直接实现的东西现在要“兜一圈”当然这里的兜一圈是打引号的,其实是循规蹈矩的,该往哪里写代码早就在事先约定好了
    2.使用了此框架后代码理解起来很费力?
    答:这一条纯属是没有框架概念的人会有的,如果你稍稍注意代码的规律,你会发现 固定的代码只出现在固定的地方
    3.断点不知往哪里打 ,说的更直白点就是小白完全追踪不到别人写的代码
    答:代码追踪其实应该是代码风格的问题,这个很大程度上是人为的把Signal或IEvent写的满天飞 找不到注册的源头

    
    现在,是时候开始学习StrangeIOC,我们的目标是 完成一个 " 自动化 简洁 高效 " 的框架 。如果你感兴趣可以过来一起聊聊 StrangeIOC  137728654 。


    假定现在你已经对本框架有了初步了解,如果并未有了解也无妨可以看完本篇之后回过头再看官方的教程,比较走运的是教程我已经翻译好了,这是目录 http://www.cnblogs.com/Keyle/p/4345044.html 。

    我们先看看框架的概念图把,从这张概念图中会引申出我们要讨论的几个话题
 


话题一  模块之间划分
    模块的划分看似是个技术活,划分完成之后却又变成体力活。为什么这样说呢?原因很简单当我们在给项目分模块的时候其实也是在划分代码的服务边界,要求我们理清楚代码的调用关系,让其符合MVC的概念。当我们划分完成之后每次新建一个类的时候都需要新建一个对应的Mediator有时候还要新建一个Command与一个Model,每次新建一个View可能需要重复此步骤,当然现在我正在开发的工具就是为了省去这个中间步骤 一键生成代码。听起来是很爽的样子 。模块划分应该是项目开启的第一步,这个划分应该把握住一点 分清模块的职责不要混在一起,你不能说我们的一个模块是负责XX与XX和XX,你应该说  我们的一个模块只负责XX

话题二 View驱动方式的抉择
    大的模块划分要求我们符合MVC概念,但是在模块内我们还是要自己维护内部的调用关系,关于View的驱动方式,我最先想到的是通过Cammand驱动 或是 通过Model驱动,先说Command驱动方式,我们可以想象成把传统的三层架构的Logic层放在Command,数据来自Command对Model的查询,这是变成了 Command 决策View的行为并提供数据,而View与Model没有交互,再聊第二种数据驱动,也就是Model驱动界面,给View建一个对应的Model保存View的状态,每次刷新都只需要View对外提供一个Refresh接口便可以刷新View的信息,实际开发过程中这个抉择似乎没有绝对的,有时候哪种实现方式方便用哪一种,而我们新建的实体Model也将不纳入全局的Model,而作为模块级别的ViewModel 。(所谓全局的Model一般指策划配置的数据或是数据源)


话题三  模块之间通讯
    StrangeIOC提供的消息总线有两种 一种是IEvent传递,还有一种是通过Signal传递,我们写代码的时候只需要选择一种消息总线的实现方式即可,使用IEvent的好处是 自己可以继承此接口自己实现自定义的消息体,譬如自定义一个传递消息的类可以包括任意多的自己需要传递的类,缺陷是 每次接收到消息的时候都是以IEvent接口的形式 势必需要 as 一次或者直接强转一次,首先这是一种类型不安全的做法,同时也会造成不必要的性能损耗。Signal是StrangeIOC框架成熟后提供的,Signal的好处是强类型 ,类型安全 也不需要强转,你只需要在接收的地方Inject属性即可,如果硬要说使用Signal的不好的话,我想应该是如果你在调用的地方注入了一个Signal的强类型列表中不存在的类型,会报错, 错误信息是 InjectionException: Attempt to Instantiate a null binding. 试图实例化一个空的绑定。意为在绑定列表中未找到与此属性对应的类型注入,也就是没有Bind到IOC容器内却被调用导致为空。    

几个概念

想要熟悉StrangeIOC那么话题三中我们提到几个概念 注入 绑定 IOC容器 是必须要懂的,那下面我们开始讲讲上面提到的几个概念

1. IOC容器 IOC也被成为控制反转,在StrangeIOC与许许多多框架通常用这种技巧实现一个容器,我们将代码内的依赖关系交给第三方(IOC容器)管理,需要什么类型告诉工厂你要的类型,他会生产给你一个实例,简而言之我们可以看作是一个用来new类型的工厂(参见设计模式之简单工厂)

2.Bind 我们可以理解为往这个IOC工厂内添加接口与类型的映射

3.注入 也叫依赖注入(DI),这个注入的过程就是从IOC容器内取值的过程,注入的方式有很多 属性注入,接口注入,方法注入,而下文我们着重看看如何实现一个属性注入,StrangeIOC就是使用属性注入,这也是很多初学者疑惑的地方,为什么我写了一个[Inject]就能获取到实例。

实现属性注入

以下代码用以模拟注入标签,当然实际框架中也是这样做的,现在把你向注入的类型都打上标签把~

    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
    sealed class InjectAttribute : Attribute
    {
        readonly string positionalString;
        public InjectAttribute(string injectType)
        {
            positionalString = injectType;
        }
        public InjectAttribute()
        {
        }
    }

以下代码用以实现一个简单注入工厂 此类是一个单例(通过属性器实现),来模拟注入框架,BindCache 为注入的类型列表,这里我用泛型Bind来添加BindCache

public class InjectorFactory
    {
        private static InjectorFactory instance;
        public static InjectorFactory Instance
        {
            get { return instance ?? (instance = new InjectorFactory()); }
        }
        private InjectorFactory() { BindCache = new Dictionary<Type, Type>(); }
        public Dictionary<Type, Type> BindCache { get; set; }
        public void Bind<T, V>()
        {
            if (!BindCache.ContainsKey(typeof(T)))
            {
                BindCache.Add(typeof(T), typeof(V));
            }
            else
            {
                BindCache[typeof(T)] = typeof(V);
            }
        }

        public T CreateInstance<T>() where T : new()
        {
            var a = new T();

            //注入此类内部属性
            KeyValuePair<Type, PropertyInfo>[] pairs = new KeyValuePair<Type, PropertyInfo>[0];
            object[] names = new object[0];
            MemberInfo[] privateMembers = a.GetType().FindMembers(MemberTypes.Property,
                                                    BindingFlags.FlattenHierarchy |
                                                    BindingFlags.SetProperty |
                                                    BindingFlags.Public |
                                                    BindingFlags.NonPublic |
                                                    BindingFlags.Instance,
                                                    null, null);

            foreach (MemberInfo member in privateMembers)
            {
                object[] injections = member.GetCustomAttributes(typeof(InjectAttribute), true);
                if (injections.Length > 0)
                {
                    PropertyInfo point = member as PropertyInfo;
                    Type pointType = point.PropertyType;
                    point.SetValue(a, Activator.CreateInstance(BindCache[pointType]));
                }
            }
            return a;
        }
    }

既然要注入怎么少的了一个基类或一个接口呢

     public interface IMyClass
    {
        string GetName();
    }

我们用以下两个类去实现这个接口

    public class MyClass1 : IMyClass
    {
        public string GetName()
        {
            return "Keyle Is Inject MyClass1 ...";
        }
    }
    public class MyClass2 : IMyClass
    {
        public string GetName()
        {
            return "Keyle Is Inject MyClass2 ...";
        }
    }


编写一个测试类,我们注入它
    public class Test
    {
        [Inject]
        public IMyClass cls { get; set; }
    }


测试以下我们实现的注入工厂

        static void Main(string[] args)
        {
            //注入类类型
            InjectorFactory.Instance.Bind<IMyClass, MyClass2>();
            //进入框架生命周期
            var test = InjectorFactory.Instance.CreateInstance<Test>();
            //调用此类的方法GetName
            Console.WriteLine(test.cls.GetName());
            Console.ReadKey(); 
        }

测试结果,当然如果你把上面的Bind改成Bind<IMyClass, MyClass1>则下面也会变成 Keyle Is Inject MyClass1
file:///C:/Users/Fez/AppData/Local/YNote/data/1994160065@163.com/1493fb0733da4123bb207f37e932abab/clipboard.png
 

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台