聊聊C#中的Mixin的具体用法

目录
  • 写在前面
  • 从一个简单例子说起
  • 在类中实现单例
  • 在父类中实现单例
  • 轮到Mixin出场
    • 定义
    • Mixin在C#中
    • 在8.0之前
  • 从C#8.0开始

写在前面

Mixin本意是指冰淇淋表面加的那些草莓酱,葡萄干等点缀物,它们负责给冰淇淋添加风味。在OOP里面也有Mixin这个概念,和它的本意相似,OOP里面的Mixin意在为类提供一些额外功能——在不破坏类本身或者它的继承链的基础上,在某些情况下可能会起到妙用。今天跟着小编一起来看看吧。

从一个简单例子说起

试想我们在写一个游戏引擎,创建如下类:

    class ScriptManager
    {
        public void AddScript(){/*省略实现*/}

        public void RemoveScript(){/*省略实现*/}
    }

    class EntityManager
    {
        public void AddEntity() {/*省略实现*/}

        public void RemoveEntity() {/*省略实现*/}
    }

    class AnimationManager
    {
        public void AddAnimationToWorld() {/*省略实现*/}

        public void RemoveAnimationFromWorld() {/*省略实现*/}
    }

代码非常简单,三个manager类分别控制脚本、实体和动画。但是我们突然发现,这三个类应该都是单例才合适。按照我们之前在C#中的Singleton中介绍的方法,我们这么改写一下这三个类。

在类中实现单例

最简单的,我们可以这么改

    class ScriptManager
    {
        private static ScriptManager _instance = null;
        public static ScriptManager Instance
        {
            get
            {
                if(_instance == null)
                {
                    lock(typeof(ScriptManager))
                    {
                        if(_instance == null)
                        {
                            _instance = new ScriptManager();
                        }
                    }
                }
                return _instance;
            }
        }
        public void AddScript(){/*省略实现*/}

        public void RemoveScript(){/*省略实现*/}
        private ScriptManager() {/*省略实现*/} //车门焊死,不让外部调用
    }

	class EntityManager
	{
		//类似的修改方法
	}

	class AnimationManager
	{
		//类似的修改方法
	}

    static void Main(string[] args)
    {
        var instance1 = ScriptManager.Instance;
        var instance2 = ScriptManager.Instance;
        var result = instance1 == instance2; //true
    }

看起来没有什么问题,确实也满足了可用的要求,但是仅仅可用是不够的,我们想要更好的解决方案,而且这种修改方法虽然简单,但如果我们想要修改的类不止这三个,或者,我们想要添加的不仅仅是单例方法,我们需要写的代码会成倍增加,所以我们想要更好的解决方案。

在父类中实现单例

很容易就能想到,既然这块代码逻辑都是一样的,我们为什么不把它提炼到父类?像这样

    class SingletonHolder<T>
        where T : class
    {
        private static T _instance = null;
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (typeof(T))
                    {
                        if (_instance == null)
                        {
                            _instance = (T)Activator.CreateInstance(typeof(T), true); //调用非公有构造器
                        }
                    }
                }
                return _instance;
            }
        }
    }

    class ScriptManager : SingletonHolder<ScriptManager>
    {
		//省略
    }

    class EntityManager : SingletonHolder<EntityManager>
    {
		//省略
    }

    class AnimationManager : SingletonHolder<AnimationManager>
    {
		//省略
    }

    static void Main(string[] args)
    {
        var ScriptManager1 = ScriptManager.Instance;
        var ScriptManager2 = ScriptManager.Instance;
        var result = ScriptManager1 == ScriptManager2; //true

        var EntityManager1 = EntityManager.Instance;
        var EntityManager2 = EntityManager.Instance;
        result = EntityManager1 == EntityManager2; //true

        var AnimationManager1 = AnimationManager.Instance;
        var AnimationManager2 = AnimationManager.Instance;
        result = AnimationManager1 == AnimationManager2; //true
    }

确实可以,这样就算有再多的类需要实现单例,只要让它们继承SingletonHolder就可以了,这样的代码方便扩展也方便维护,毕竟功能逻辑都在父类里面。

不过仔细想想,这样的代码还是有点问题,类继承意味着子类应该是父类的特化,代表着一种is-a的关系,但是我们这几个Manager类和SingletonHolder并不是这种关系,它们和SingletonHolder更多像是一种实现契约的关系;如果一定要说is-a,它们应该是引擎模块(ModuleManager)的一种特化。所以让它们继承自SingletonHolder其实不是最好的方法,虽然语法正确、行为正确但是并不是语义正确,作为程序员,我们应该追求尽善尽美。而且未来真有可能会抽象出一个父类ModuleManager,到时候就发现唯一的类继承名额已经给SingletonHolder给占用了,所以我们需要寻找一种既能注入逻辑代码,又不涉及类继承的方法。

轮到Mixin出场

定义

In object-oriented programming languages, a mixin (or mix-in) is a class that contains methods for use by other classes without having to be the parent class of those other classes. How those other classes gain access to the mixin's methods depends on the language. Mixins are sometimes described as being "included" rather than "inherited".
Mixins encourage code reuse and can be used to avoid the inheritance ambiguity that multiple inheritance can cause (the "diamond problem"), or to work around lack of support for multiple inheritance in a language. A mixin can also be viewed as an interface with implemented methods. This pattern is an example of enforcing the dependency inversion principle.

这是在Wiki上面Mixin的定义,允许程序员以在类继承之外的方式为类添加一些方法,即,既能为类提供方法实现,又可以避免成为类的父类,避免了类继承和多重继承所带来的问题,这种概念正是我们需要的。

Mixin在C#中

在C#中,它们通常以拥有实现的接口出现(default implementation interface from C#8.0),而在C#8.0之前,我们通常以辅助类的方式来实现Mixin,我们下面以这两种方式改写之前的类。

在8.0之前

我们定义出一个接口,然后在外部基于这个接口实现单例逻辑(不用扩展方法是因为扩展方法不支持static method,如果想要注入的是非static method可以使用基于接口的扩展方法)

    class SingletonHolder<T>
        where T : class, ISingleton
    {
        private static T _instance = null;
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (typeof(T))
                    {
                        if (_instance == null)
                        {
                            _instance = (T)Activator.CreateInstance(typeof(T), true);
                        }
                    }
                }
                return _instance;
            }
        }
    }

    interface ISingleton
    {
        //没有任何方法因为只是一个标记
    }

    class ScriptManager : ISingleton
    {
        private ScriptManager() {/*省略实现*/}
        public void AddScript(){/*省略实现*/}

        public void RemoveScript(){/*省略实现*/}
    }

    class EntityManager : ISingleton
    {
        private EntityManager() {/*省略实现*/}
        public void AddEntity() {/*省略实现*/}

        public void RemoveEntity() {/*省略实现*/}
    }

    class AnimationManager : ISingleton
    {
        private AnimationManager() {/*省略实现*/}
        public void AddAnimationToWorld() {/*省略实现*/}

        public void RemoveAnimationFromWorld() {/*省略实现*/}
    }

    static void Main(string[] args)
    {
        var ScriptManager1 = SingletonHolder<ScriptManager>.Instance;
        var ScriptManager2 = SingletonHolder<ScriptManager>.Instance;
        var result = ScriptManager1 == ScriptManager2; //true

        var EntityManager1 = SingletonHolder<EntityManager>.Instance;
        var EntityManager2 = SingletonHolder<EntityManager>.Instance;
        result = EntityManager1 == EntityManager2; //true

        var AnimationManager1 = SingletonHolder<AnimationManager>.Instance;
        var AnimationManager2 = SingletonHolder<AnimationManager>.Instance;
        result = AnimationManager1 == AnimationManager2; //true
    }

这就是Mixin的用处,看起来这种实现方式的好处有:

  • 类只需要声明实现ISingleton即可完成单例相关编码
  • ISingleton是接口,类可以声明实现多个接口而不会有类继承的单一限制,同时也不会有那种is-a的类继承烦恼
  • ISingleton是空接口,任何类实现它不需要额外的对该类自身的修改,就像淋上草莓酱不会对冰淇淋本身造成影响一样,符合开闭原则

从C#8.0开始

从C#8.0开始,接口可以有方法的默认实现(包括static method),我们可以更加简单的实现Mixin解决之前的问题

    interface SingletonHolder<T>
        where T:class
    {
        private static T _instance = null;
        static T Instance
        {
            get
            {
                if(_instance == null)
                {
                    lock(typeof(T))
                    {
                        if(_instance == null)
                        {
                            _instance = (T)Activator.CreateInstance(typeof(T), true);
                        }
                    }
                }
                return _instance;
            }
        }
    }
    class ScriptManager : SingletonHolder<ScriptManager>{}
    class EntityManager : SingletonHolder<EntityManager>{}
    class AnimationManager : SingletonHolder<AnimationManager>{}

这就是Mixin以及它在C#中的简单使用方法,希望通过这篇介绍能让大家对这种用法有所了解,在想要给类添加代码逻辑但是又不想改变类内部或者影响类的继承体系的时候,使用Mixin这种基于接口的代码逻辑注入也许能有奇效哦。

到此这篇关于聊聊C#中的Mixin的具体用法的文章就介绍到这了,更多相关C# Mixin用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#中缓存的基本用法总结

    本文初步探讨了C#缓存的原理及应用,并以实例加以分析总结,这些对C#初学者来说是很有必要熟练掌握的内容.具体如下: 一.概述: 缓存应用目的:缓存主要是为了提高数据的读取速度.因为服务器和应用客户端之间存在着流量的瓶颈,所以读取大容量数据时,使用缓存来直接为客户端服务,可以减少客户端与服务器端的数据交互,从而大大提高程序的性能. 1.缓存的引用空间:System.Web.Caching; 缓存命名空间主要提供三种操作:缓存数据对象.对象的缓存依赖和数据库的缓存依赖.其中缓存任何对象都使用一个类C

  • C#中string.format用法详解

    本文实例总结了C#中string.format用法.分享给大家供大家参考.具体分析如下: String.Format 方法的几种定义: String.Format (String, Object) 将指定的 String 中的格式项替换为指定的 Object 实例的值的文本等效项. String.Format (String, Object[]) 将指定 String 中的格式项替换为指定数组中相应 Object 实例的值的文本等效项. String.Format (IFormatProvide

  • C#中HttpWebRequest的用法详解

    本文实例讲述了C#中HttpWebRequest的用法.分享给大家供大家参考.具体如下: HttpWebRequest类主要利用HTTP 协议和服务器交互,通常是通过 GET 和 POST 两种方式来对数据进行获取和提交.下面对这两种方式进行一下说明: GET 方式: GET 方式通过在网络地址附加参数来完成数据的提交,比如在地址 http://www.jb51.net/?hl=zh-CN 中,前面部分 http://www.jb51.net表示数据提交的网址,后面部分 hl=zh-CN 表示附

  • C# List<T>的用法小结

    所属命名空间:System.Collections.Generic     public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable List<T>类是 ArrayList 类的泛型等效类.该类使用大小可按需动态增加的数组实现 IList<T> 泛型接口. 泛型的好处: 它为使用c#语言编写面向对象程

  • C#中using的三种用法

    using 指令有两个用途: 允许在命名空间中使用类型,以便您不必限定在该命名空间中使用的类型. 为命名空间创建别名. using 关键字还用来创建 using 语句  定义一个范围,将在此范围之外释放一个或多个对象. 请参见 using 语句.http://www.yaosansi.com/blog/article.asp?id=669 using namespace; using alias = type|namespace;  参数 Alias  您希望用来表示命名空间或类型的用户定义符号

  • C#中Dictionary的作用及用法讲解

    Dictionary<string, string>是一个泛型 他本身有集合的功能有时候可以把它看成数组 他的结构是这样的:Dictionary<[key], [value]> 他的特点是存入对象是需要与[key]值一一对应的存入该泛型 通过某一个一定的[key]去找到对应的值 举个例子: 复制代码 代码如下: //实例化对象 Dictionary<int, string> dic = new Dictionary<int, string>(); //对象打

  • C#中yield return用法分析

    本文实例讲述了C#中yield return用法,并且对比了使用yield return与不使用yield return的情况,以便读者更好的进行理解.具体如下: yield关键字用于遍历循环中,yield return用于返回IEnumerable<T>,yield break用于终止循环遍历. 有这样的一个int类型的集合: static List<int> GetInitialData() { return new List<int>(){1,2,3,4}; }

  • c#中LINQ的基本用法实例

    一.什么是LINQ LINQ(读音link)代表语言集成查询(Language Integrated Query),是.NEt框架的扩展,它允许我们用SQL查询数据库的方式来查询数据的集合,使用它,你可以从数据库.程序对象的集合以及XML文档中查询数据 下面一个简单的示例,可以查询数组中小于8的数字并输出. 一般步骤:获取数据源.创建查询.执行查询.需要注意的是,尽管查询在语句中定义,但直到最后的foreach语句请求其结果的时候才会执行 using System; using System.C

  • 聊聊C#中的Mixin的具体用法

    目录 写在前面 从一个简单例子说起 在类中实现单例 在父类中实现单例 轮到Mixin出场 定义 Mixin在C#中 在8.0之前 从C#8.0开始 写在前面 Mixin本意是指冰淇淋表面加的那些草莓酱,葡萄干等点缀物,它们负责给冰淇淋添加风味.在OOP里面也有Mixin这个概念,和它的本意相似,OOP里面的Mixin意在为类提供一些额外功能——在不破坏类本身或者它的继承链的基础上,在某些情况下可能会起到妙用.今天跟着小编一起来看看吧. 从一个简单例子说起 试想我们在写一个游戏引擎,创建如下类:

  • 详细聊聊sql中exists和not exists用法

    目录 exists: exists 和in 的区别 not exists详细介绍: 附案例分析 总结 之所以要说这个问题,是因为项目中用到了not exists,但两者写的语句只有一点差别,结果一个有问题了,一个没问题.具体问题下面详细说明,先来看看exists如何应用. exists: 强调的是是否有返回集,不需知道具体返回的是什么,比如: SELECT * FROM customer WHERE not EXISTS ( SELECT 0 FROM customer_goods WHERE

  • 聊聊PHP中die()和sleep()函数的用法

    在上一篇<聊聊PHP中删除字符串的逗号和尾部斜杠的方法>给大家介绍了PHP删除字符串中的逗号以及尾部斜杠的方法,感兴趣的朋友可以去学习了解一下~ 本文也将给大家通过示例来讲解标题所述"PHP中die()和sleep()函数的用法". 一.关于die()函数的用法 die()是在PHP一个内置功能.它用于打印消息并退出当前的 php 脚本.相当于PHP 中的exit()函数. 语法很简单,如"die($message)" die()函数只接受一个参数,并且

  • 聊聊PHP中require_once()函数为什么不好用

    在上一篇<聊聊PHP中die()和sleep()函数的用法>中给大家简单介绍了die()和sleep()函数的使用方法,感兴趣的朋友可以去学习了解一下~ 本文将告诉你PHP中require_once()为什么不好用! 不过在说它不好用之前,我们先开看看require_once()函数的定义和用法. require_once()函数是PHP中的内置函数,当我们想要将一个PHP文件引入到另一个文件中时,例如当我们需要在PHP脚本中多次引入一个文件时,它就非常有用了.它用于检查文件是否被包含了不止一

  • Vue中的常用指令及用法总结

    首先来聊聊Vue框架,Vue是一套用于构建用户界面的渐进式的JavaScript框架,对于初学者来说是非常友好的 , Vue的虚拟Dom , 数据双向绑定 , 都使开发者可以快速上手 , 而我个人感觉 , Vue的指令使用起来非常的方便 , 今天的这篇文章们就来聊聊常用的Vue指令吧! v-for 在初学任何一门语言的时候我们或多或少都接触过 for for (let i = 0; i < arr.length; i++) { } Vue 的 v-for跟我们js里面的最根本的概念还是一样的就是

  • 聊聊Kotlin 中 lateinit 和 lazy 的原理区别

    目录 lateinit 用法 原理 lazy 用法 原理 the end references 使用 Kotlin 进行开发,对于 latelinit 和 lazy 肯定不陌生.但其原理上的区别,可能鲜少了解过,借着本篇文章普及下这方面的知识. lateinit 用法 非空类型可以使用 lateinit 关键字达到延迟初始化. class InitTest() { lateinit var name: String ​ public fun checkName(): Boolean = name

  • vue3.0中setup的两种用法实例

    目录 前言 一.setup函数的特性以及作用 二.setup函数的注意点: 用法1:结合ref使用 用法2:代码分割 总结 前言 这篇文章主要介绍了vue3.0中setup使用,本文通过两种用法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 什么是setup setup是vue3新增的生命周期函数,setup的加入就是为了让vue3使用组合式API(Composition API).使用组合式API更符合大型项目的开发,通过setup可以将该部分抽离成函数,

  • Angular中$cacheFactory的作用和用法实例详解

    先说下缓存: 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能够服务的请求越多,整体系统性能就能提升越多. $cacheFactory介绍: $cacheFactory是一个为Angular服务生产缓存对象的服务.要创建一个缓存对象,可以使用$cacheFactory通过一个ID和capacity.其中,ID是一个缓存对象的名称,capacity则是描述缓存键值对的最大数量. 1.

  • JSP 中request与response的用法详解

    JSP 中request与response的用法详解 概要: 在学习这两个对象之前,我们应该已经有了http协议的基本了解了,如果不清楚http协议的可以看我的关于http协议的介绍.因为其实request和response的使用大部分都是对http协议的操作. request对象的介绍 我们先从request对象进行介绍: 我们知道http协议定义了请求服务器的格式: 请求行 请求头 空格 请求体(get请求没有请求体) 好了,这里我们就不详细介绍了,我们只看几个应用就可以了,没什么难度: 应

  • jsp中select的onchange事件用法实例

    本文实例讲述了jsp中select的onchange事件用法.分享给大家供大家参考,具体如下: <script language = "JavaScript"> var onecount; onecount=0; subcat = new Array(); <% int count = 0; java.sql.ResultSet rs1 = DBManage.executeQuery("select hydm,zhydm,zhymc from zhy &qu

随机推荐