Mixin

Mixin 就是 混入的意思,主要是为了解决多重继承 带来复杂继承链的问题,或者说是多重继承实现的一种技巧
https://www.liaoxuefeng.com/wiki/897692888725344/923030524000032
https://www.zhihu.com/question/20778853

Mixin
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin。



为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixin和FlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin:



class Dog(Mammal, RunnableMixin, CarnivorousMixin):
pass
Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。



作者:Wealong C
链接:https://www.zhihu.com/question/20778853/answer/55983839
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



谈到Mixin就不得不谈到多重继承,因为Mixin的出现就是为了解决多重继承的问题,那么多重继承有什么问题呢?在《松本行弘的程序世界》一书中,作者列举了以下三点:结构复杂化:如果是单一继承,一个类的父类是什么,父类的父类是什么,都很明确,因为只有单一的继承关系,然而如果是多重继承的话,一个类有多个父类,这些父类又有自己的父类,那么类之间的关系就很复杂了。优先顺序模糊:假如我有A,C类同时继承了基类,B类继承了A类,然后D类又同时继承了B和C类,所以D类继承父类的方法的顺序应该是D、B、A、C还是D、B、C、A,或者是其他的顺序,很不明确。功能冲突:因为多重继承有多个父类,所以当不同的父类中有相同的方法是就会产生冲突。如果B类和C类同时又有相同的方法时,D继承的是哪个方法就不明确了,因为存在两种可能性。当然你可以说有些语言解决了这个问题,但是并不是所有语言都想要去纠结这个问题。所以为能够利用多继承的优点又解决多继承的问题,提出了规格继承和实现继承这两样东西。简单来讲,规格继承指的是一堆方法名的集合,而实现继承除了方法名还允许有方法的实现。Java 选择了规格继承,在 Java 中叫 interface(不过Java8中已经有默认方法了),而 Ruby 选择了实现继承,也可以叫Mixin,在 Ruby 中叫 module。从某种程度上来说,继承强调 I am,Mixin 强调 I can。当你 implement 了这个接口或者 include 这个 module 的时候,然后就你行你上。所以这又可以扯到 duck typing 去了,不细说。要想了解具体的可以看一下《松本行弘的程序世界》这本书。



mixin不难,就是有点绕,今天我们探索两个问题



1、什么时候合并



2、怎么合并
https://blog.csdn.net/qq_27460969/article/details/88688073



https://zh.javascript.info/mixins



JavaScript 中的 Mixin 模式
在 JavaScript 中,我们只能继承单个对象。每个对象只能有一个 [[Prototype]] 原型。并且每个类只可以扩展另外一个类。



但是有些时候这种设定(译者注:单继承)会让人感到受限制。比如说我有一个 StreetSweeper 类和一个 Bicycle 类,现在我想要一个 StreetSweepingBicycle 类(译者注:实现两个父类的功能)。



或者,在谈论编程的时候,我们有一个实现模板的 Renderer 类和一个实现事件处理的 EventEmitter 类,现在想要把这两个功能合并到一个 Page 类上以使得一个页面可以同时使用模板和触发事件。



有一个概念可以帮助我们,叫做“mixins”。



根据维基百科的定义,mixin 是一个包含许多供其它类使用的方法的类,而且这个类不必是其它类的父类。



换句话说,一个 mixin 提供了许多实现具体行为的方法,但是我们不单独使用它,我们用它来将这些行为添加到其它类中。



一个 Mixin 实例
在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有许多实用方法的对象,通过这个对象我们可以轻易地将这些实用方法合并到任何类的原型中。



例如,这个叫做 sayHiMixin 的 mixin 用于给 User 添加一些“言语”。



// mixin
let sayHiMixin = {
sayHi() {
alert(Hello ${this.name});
},
sayBye() {
alert(Bye ${this.name});
}
};



// 用法:
class User {
constructor(name) {
this.name = name;
}
}



// 拷贝方法
Object.assign(User.prototype, sayHiMixin);



// 现在 User 可以说 hi 了
new User(“Dude”).sayHi(); // Hello Dude!
没有继承,只有一个简单的方法拷贝。因此 User 可以扩展其它类并且同样包含 mixin 来“mix-in”其它方法,就像这样:



class User extends Person {
// …
}



Object.assign(User.prototype, sayHiMixin);
Mixin 可以在自己内部使用继承。



比如,这里的 sayHiMixin 继承于 sayMixin:



let sayMixin = {
say(phrase) {
alert(phrase);
}
};



let sayHiMixin = {
proto: sayMixin, // (或者,我们可以在这里通过 Object.create 来设置原型。)



sayHi() {
// 调用父类中的方法
super.say(Hello ${this.name});
},
sayBye() {
super.say(Bye ${this.name});
}
};



class User {
constructor(name) {
this.name = name;
}
}



// 拷贝方法
Object.assign(User.prototype, sayHiMixin);



// 现在 User 可以说 hi 了
new User(“Dude”).sayHi(); // Hello Dude!
请注意在 sayHiMixin 内部对于父类方法 super.say() 的调用会在 mixin 的原型上查找方法而不是在 class 自身查找。



那是因为 sayHiMixin 内部的方法设置了 [[HomeObject]] 属性。因此 super 实际上就是 sayHiMixin.proto ,而不是 User.proto



EventMixin
现在让我们为了实际运用构造一个 mixin。



许多对象的重要特征是与事件一起工作。



也就是说:对象应该有一个方法在发生重要事件时“生成事件”,其它对象应该能够“监听”这样的事件。



一个事件必须有一个名称,并可以选择性的捆绑一些额外的数据。



比如说,一个 user 对象能够在访问者登录时产生“login”事件。另一个 calendar 对象可能在等待着接受一个这样的事件以便为登录后的用户加载日历。



或者,menu 在菜单选项被选择之后会产生 “select” 事件,并且其它对象可能在等待着接受事件的信息并且对事件做出反应。



事件是一种与任何想要得到信息的人分享信息的方式。它在任何类中都可以使用,因此现在为它构造一个 mixin。



let eventMixin = {
/**



  • 订阅事件,用法:

  • menu.on(‘select’, function(item) { … }
    */
    on(eventName, handler) {
    if (!this._eventHandlers) this._eventHandlers = {};
    if (!this._eventHandlers[eventName]) {
    this._eventHandlers[eventName] = [];
    }
    this._eventHandlers[eventName].push(handler);
    },



/**



  • 取消订阅,用法:

  • menu.off(‘select’, handler)
    */
    off(eventName, handler) {
    let handlers = this._eventHandlers && this._eventHandlers[eventName];
    if (!handlers) return;
    for (let i = 0; i < handlers.length; i++) {
    if (handlers[i] === handler) {
    handlers.splice(i–, 1);
    }
    }
    },



/**



  • 触发事件并传递参数

  • this.trigger(‘select’, data1, data2);
    */
    trigger(eventName, …args) {
    if (!this._eventHandlers || !this._eventHandlers[eventName]) {
    return; // 对应事件名没有事件处理函数。
    }



// 调用事件处理函数
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; 有三个方法:


.on(eventName, handler) — 指定函数 handler 在具有对应事件名的事件发生时运行。这些事件处理函数存储在 _eventHandlers 属性中。
.off(eventName, handler) — 在事件处理函数列表中移除指定的函数。
.trigger(eventName, …args) — 触发事件:所有被指定到对应事件的事件处理函数都会被调用并且 args 会被作为参数传递给它们。
用法:



// 新建一个 class
class Menu {
choose(value) {
this.trigger(“select”, value);
}
}
// 添加 mixin
Object.assign(Menu.prototype, eventMixin);



let menu = new Menu();



// 被选中时调用事件处理函数:
menu.on(“select”, value => alert(Value selected: ${value}));



// 触发事件 => 展示被选中的值:123
menu.choose(“123”); // 被选中的值
现在如果我们已经有了针对用户选择事件做出具体反应的代码,可以将代码使用 menu.on(…) 进行绑定。



只要我们喜欢,就可以通过 eventMixin 将这些行为添加到任意个数的类中而不影响继承链。



总结
Mixin — 是一个通用的面向对象编程术语:一个包含其他类的方法的类。



一些其它语言比如 python 允许通过多继承实现 mixin。JavaScript 不支持多继承,但是可以通过拷贝多个类中的方法到某个类的原型中实现 mixin。



我们可以使用 mixin 作为一种通过多种行为来增强类的方式,就像我们上面看到的事件处理一样。



如果 Mixins 偶尔会重写原生类中的方法,那么 Mixins 可能会成为一个冲突点。因此通常情况下应该好好考虑 mixin 的命名,以减少这种冲突的可能性。


Category lang