聊聊C#中的Mixin
写在前面
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的更多相关文章
- 简单聊聊java中的final关键字
简单聊聊java中的final关键字 日常代码中,final关键字也算常用的.其主要应用在三个方面: 1)修饰类(暂时见过,但是还没用过); 2)修饰方法(见过,没写过); 3)修饰数据. 那么,我们 ...
- 聊聊iOS中网络编程长连接的那些事
1.长连接在iOS开发中的应用 常见的短连接应用场景:一般的App的网络请求都是基于Http1.0进行的,使用的是NSURLConnection.NSURLSession或者是AFNetworking ...
- 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor
#### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...
- 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)
每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...
- 聊聊 Vue 中 axios 的封装
聊聊 Vue 中 axios 的封装 axios 是 Vue 官方推荐的一个 HTTP 库,用 axios 官方简介来介绍它,就是: Axios 是一个基于 promise 的 HTTP 库,可以用在 ...
- 聊聊 Vue 中 provide/inject 的应用
众所周知,在组件式开发中,最大的痛点就在于组件之间的通信.在 Vue 中,Vue 提供了各种各样的组件通信方式,从基础的 props/$emit 到用于兄弟组件通信的 EventBus,再到用于全局数 ...
- 聊聊GIS中的坐标系|再版
本文约6500字,建议阅读时间15分钟. 作者:博客园/B站/知乎/csdn/小专栏 @秋意正寒 版权:转载请告知,并在转载文上附上转载声明与原文链接(https://www.cnblogs.com/ ...
- 前端框架中 “类mixin” 模式的思考
"类 mixin" 指的是 Vue 中的 mixin,Regular 中的 implement 使用 Mixin 的目的 首先我们需要知道为什么会有 mixin 的存在? 为了扩展 ...
- 简单聊聊CSS中的3D技术之“立方体”
简单聊聊CSS中的3D技术之“立方体” 大家好,我是今天的男一号,我叫小博主. 今天来聊一下我在前端“逆战班”学习中遇到的颇为有趣的3D知识.前端学习3周,见识稀疏,在下面的分享中如有不对的地方请大家 ...
随机推荐
- AS的不同布局
AndroidStudio里面支持的布局有挺多种的,但是最最重要的是RelativeLayout(相对布局)和LinearLayout(线性布局),熟练掌握这两种布局也非常够用了,当然还有FrameL ...
- audio小记
写H5活动页的需要音频,图标旋转停止保持当时的旋转角度,这样视觉体验效果好: 之前写法是点击pause()就直接停止动画,后来发现了animation有个比较好的属性animation-play-st ...
- 论文解读(Graph-MLP)《Graph-MLP: Node Classification without Message Passing in Graph》
论文信息 论文标题:Graph-MLP: Node Classification without Message Passing in Graph论文作者:Yang Hu, Haoxuan You, ...
- 【零碎小bug系列】windows下的回车和换行符,cmd(telnet)上输出不左对齐
cmd(telnet)上输出不左对齐,而是有莫名其妙的空格 目录 cmd(telnet)上输出不左对齐,而是有莫名其妙的空格 背景 解决 细究 背景 在cmd上使用telnet连接本地端口的服务器时, ...
- cat /proc/cpuinfo 讲解
查看cpu信息有什么用呢,我们来看看到底有哪些用处:1.和云服务提供商核算成本,现在基本是cpu和内存的费用最大,硬盘大小几乎被忽略了2.我们写程序时候是会关注多核还是单核的,否则不能充分利用多线程等 ...
- rbac介绍、自动生成接口文档、jwt介绍与快速签发认证、jwt定制返回格式
今日内容概要 RBAC 自动生成接口文档 jwt介绍与快速使用 jwt定制返回格式 jwt源码分析 内容详细 1.RBAC(重要) # RBAC 是基于角色的访问控制(Role-Based Acces ...
- mongodb replication
官方文档:https://docs.mongodb.com/manual/replication/ 启动参数: 通过linux的包管理器(例如:yum)安装的mongodb会产生一个默认的配置文件:/ ...
- Python 国家地震台网中心地震数据集完整分析、pyecharts、plotly,分析强震次数、震级分布、震级震源关系、发生位置、发生时段、最大震级、平均震级
注意,本篇内容根据我老师布置的数据分析作业展开.请勿抄袭,后果自负! 前情提要 编写这篇文章是为了记录自己是如何分析地震数据集,使用模块,克服一系列 \(bug\) 的过程.如果你是 \(python ...
- SpringBoot 入门及配置
1.首先创建Maven文件,右键单击鼠标,others搜索Maven,点击Maven Project 2.点击下一步,打上勾下一步,输入包名和项目名称 3.第三步修改pom.xml导入Maven依赖M ...
- 新手小白入门C语言第五章:C存储类
一 .存储类 在理解C的存储类之前,首先要搞懂的概念有:作用域.生存周期.连接属性 C中的存储类说明符实际上是上述属性的不同组合 作用域:一个C变量的作用域可以是 代码块作用域(在函数内部代码块中定义 ...