Write in front
Mixin originally means the strawberry jam, raisins and other embellishments added to the surface of the ice cream, which are responsible for adding flavor to the ice cream. There is also the concept of Mixin in OOP, which is similar to its original intention. Mixin in OOP is intended to provide some additional functions for classes - it may play a wonderful role in some cases without destroying the class itself or its inheritance chain. Let’s take a look with the editor today.
Start with a simple example
Imagine we are writing a game engine and creating the following class:
class ScriptManager { public void AddScript(){/*Omitted implementation*/} public void RemoveScript(){/*Omitted implementation*/} } class EntityManager { public void AddEntity() {/*Omitted implementation*/} public void RemoveEntity() {/*Omitted implementation*/} } class AnimationManager { public void AddAnimationToWorld() {/*Omitted implementation*/} public void RemoveAnimationFromWorld() {/*Omitted implementation*/} }
The code is very simple, and the three manager classes control scripts, entities and animations respectively. But we suddenly found that these three categories should be singletons. As we wereSingleton in C#Let’s rewrite these three categories in this way.
Implementing a single case in a class
The simplest thing, we can change it like this
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(){/*Omitted implementation*/} public void RemoveScript(){/*Omitted implementation*/} private ScriptManager() {/*Omitted implementation*/} //The door is welded to prevent external calls } class EntityManager { //Similar modification method } class AnimationManager { //Similar modification method } static void Main(string[] args) { var instance1 = ; var instance2 = ; var result = instance1 == instance2; //true }
It seems that there is no problem, and it does meet the available requirements, but it is not enough to just use it. We want a better solution. Although this modification method is simple, if we want to modify more than these three classes, or, we want to add more than just a singleton method, the code we need to write will increase exponentially, so we want a better solution.
Implementing a singleton in the parent class
It is easy to think that since the logic of this piece of code is the same, why don’t we refine it to the parent class? Like this
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)(typeof(T), true); //Calling the non-public constructor } } } return _instance; } } } class ScriptManager : SingletonHolder<ScriptManager> { //Omitted } class EntityManager : SingletonHolder<EntityManager> { //Omitted } class AnimationManager : SingletonHolder<AnimationManager> { //Omitted } static void Main(string[] args) { var ScriptManager1 = ; var ScriptManager2 = ; var result = ScriptManager1 == ScriptManager2; //true var EntityManager1 = ; var EntityManager2 = ; result = EntityManager1 == EntityManager2; //true var AnimationManager1 = ; var AnimationManager2 = ; result = AnimationManager1 == AnimationManager2; //true }
It is true, even if there are many classes that need to implement singletons, just let them inheritSingletonHolderThat's fine. Such code is easy to extend and maintain. After all, the functional logic is in the parent class.
But if you think about it carefully, there is still some problem with such code. Class inheritance means that the subclass should be the specialization of the parent class, which represents a kind ofis-abut our Manager classes and SingletonHolder are not in this relationship. They are more like a contractual relationship with SingletonHolder; if you must sayis-a, they should be a specialization of engine modules (ModuleManager). So letting them inherit from SingletonHolder is not the best way. Although the syntax and behavior are correct, the semantics are not correct. As programmers, we should pursue perfection. Moreover, in the future, it is really possible to abstract a parent class ModuleManager. At that time, we will find that the only class inheritance quota has been occupied by SingletonHolder, so we need to find a method that can inject logical code without involving class inheritance.
Mixin's turn to play
definition
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.
This is on the wikiMixinThe definition of , allows programmers to add some methods to the class in a way other than class inheritance, that is, it can not only provide method implementation for the class, but also avoid becoming the parent class of the class, avoiding the problems brought about by class inheritance and multiple inheritance. This concept is exactly what we need.
Mixin in C#
In C#, they usually appear as interfaces that have implementations (default implementation interface from C#8.0), and before C# 8.0, we usually implemented Mixin in the form of helper classes. We will rewrite the previous class in these two ways below.
Before 8.0
We define an interface and then implement singleton logic based on this interface externally (the extension method is not used because the extension method does not support static method. If you want to inject a non-static method, you can use the interface-based extension 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)(typeof(T), true); } } } return _instance; } } } interface ISingleton { //No way because it's just a mark } class ScriptManager : ISingleton { private ScriptManager() {/*Omitted implementation*/} public void AddScript(){/*Omitted implementation*/} public void RemoveScript(){/*Omitted implementation*/} } class EntityManager : ISingleton { private EntityManager() {/*Omitted implementation*/} public void AddEntity() {/*Omitted implementation*/} public void RemoveEntity() {/*Omitted implementation*/} } class AnimationManager : ISingleton { private AnimationManager() {/*Omitted implementation*/} public void AddAnimationToWorld() {/*Omitted implementation*/} public void RemoveAnimationFromWorld() {/*Omitted implementation*/} } 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 }
This is what Mixin is used for, it seems that the benefits of this implementation are:
- The class only needs to declare the implementation of ISingleton to complete singleton-related encoding.
- ISingleton is an interface, a class can declare multiple interfaces without a single limitation of class inheritance, and there will be no such class inheritance troubles of is-a
- ISingleton is an empty interface. Any class implementing it does not require additional modifications to the class itself. Just like dripping with strawberry jam will not affect the ice cream itself, it conforms to the principle of opening and closing.
Starting with C# 8.0
Starting from C# 8.0, the interface can have default implementations of methods (including static method), and we can implement Mixin more easily to solve previous problems.
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)(typeof(T), true); } } } return _instance; } } } class ScriptManager : SingletonHolder<ScriptManager>{} class EntityManager : SingletonHolder<EntityManager>{} class AnimationManager : SingletonHolder<AnimationManager>{}
This is Mixin and its simple usage method in C#. I hope this introduction will give you some understanding of this usage. When you want to add code logic to a class but don’t want to change the internals of the class or affect the inheritance system of the class, using Mixin, interface-based code logic injection may be effective.
This is the end of this article about talking about the specific usage of Mixin in C#. For more related content on the usage of Mixin, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!