-
Notifications
You must be signed in to change notification settings - Fork 383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mixin、多重继承与装饰者模式 #97
Comments
然后这个东西在stateless function component里面废了,React后来又推荐使用stateless function component了 |
同名方法返回值方面,我调用了合并后的方法,那么方法的返回值到底是mixin里的还是后面声明的? |
@ian4hu 这个问题我没明白,请描述清楚一些。 |
比如 class mixin1 {
getName() {
return 'mixin'
}
}
class A {
mixins: [mixin1]
getName() {
return 'a'
}
} 这时候 let a = new A(), a.getName() 返回值是什么 |
@ian4hu ES6的class语法并不支持mixin,你为什么要这样写? |
只是一个示例,不在于ES6语法 |
@ian4hu 我明白你的意思了,你想问“同名属性”是否覆盖对吧? 这个问题没有固定的答案,因为mixin总是人为地实现的,无论是否覆盖,都不改变混入的本质。是否覆盖,关键就是看你在实现mixin函数的时候有没有进行同名属性的判断。比如下面这种就不会覆盖。 function mixin(sourceObj, targetObj){
for(let key in sourceObj){
if(!(key in targetObj)){ // 当然,你要是喜欢把这行去掉,那就是覆盖同名属性了。
targetObj[key] = sourceObj[key];
}
}
return targetObj;
} |
@youngwind 我明白了,这个其实不是语言规范里的 所以要视具体实现而定 |
mark |
在最后一个例子当中, |
@ihaichao 是的,doSomething确实是改变了。 function() {
console.log(1);
} 前后的主要区别在于:用一个新的函数包裹原函数,然后把doSomething指向这个新函数。 |
讲的很清晰,同时也发现自己对这些js中的设计模式这方面的不足,要重新翻翻书了 |
看到这儿,忍不住评论下。写得很棒,把许多问题联系起来看。装饰器在Angular中也有广泛应用,给组件装上不同的@component,@directive 等装饰器,赋予不同的临时功能。 还能想起曾探讲的 给飞机装上原子弹的例子。学习了:) |
@youngwind 你好,关于你例子中的“寄生式继承”,代码是自己写的吗?myCar instanceof Car 返回false,因为在这个过程中myCar的__proto__已经是Vehicle的prototype了。不了解寄生式继承,难道寄生式继承就是这个样子吗?有什么存在的价值?? |
|
1 similar comment
|
assign方法只能拷贝对象上可枚举的属性和方法,对于不可枚举的属性和继承的属性是无法拷贝的,所以无法拷贝原型上的方法,你可以自己写一个例子测试 一下
…------------------ 原始邮件 ------------------
发件人: "oys9527"<notifications@github.com>;
发送时间: 2019年6月19日(星期三) 下午4:27
收件人: "youngwind/blog"<blog@noreply.github.com>;
抄送: "Subscribed"<subscribed@noreply.github.com>;
主题: Re: [youngwind/blog] Mixin、多重继承与装饰者模式 (#97)
assign方法可以继承原型上的方法的吧
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
疑问
最早接触mixin这个概念,是在使用React的时候。那时候对mixin的认知是这样的:“React不同的组件类可能需要相同的功能,比如一样的
getDefaultProps
、componentDidMount
等。出处: React Mixin 的使用
那时候我以为mixin是React独创的一个概念,直到后来我在另外的很多资料中也发现mixin的踪影,比如Vue中也有mixin。仔细研究了一番,才猛地发现:原来mixin是一种模式,有着广泛的应用。
下面我们就来一一理清思路。
Mixin的由来
关于JS中mixin是怎么来的,有两派观点。一派是模仿类,另一派是多重继承。
模仿类
众所周知,JS没有真正意义的类。为什么这门语言在设计之初就没有类?为什么非要通过”别扭“的原型链来实现继承?阮一峰老师的这篇文章给出了答案。真正的类在继承中是复制的。因此,虽然JS中没有类,但是无法阻挡众多JS开发者模仿类的复制行为,由此引入了mixin(混入)。
下面举个例子。Vehicle是一个交通工具”类“,Car是一个小汽车”类“,Car要继承于Vehicle,最常见的方法应该是通过原型链。但是,下面的代码却没有这么做。
仔细观察上面的代码,我们能够发现:Vehicle类的方法都被复制到Car的属性中(当然,避开了同名属性)。进一步思考,我们最终想要得到的结果是Car能够访问到原先Vehicle有的属性,比如ignition。
so,这就是为了模仿类而引入的mixin。下面我们来看另一派的观点。
多重继承
在面向对象的语言中(如C++、Java和JavaScript),单一继承都是非常常见的。但是,如果想同时继承多于一个结构,例如”猫“在继承”动物“的同时,又想继承”宠物“,那怎么办?
C++给出的答案是:多重继承。
然而,多重继承也是一把双刃剑。它在解决问题的同时,却又增加了程序的复杂性和含糊性,最为典型的当属钻石问题。因此,Java和JavaScript都不支持多重继承。然而,多重继承所要解决的问题依然存在,那么,他们各自又是如何解决的呢?
Java的解决方案是:通过原生的接口继承来间接实现多重继承。
JS的解决方案是:原生JS没有解决方案(别忘了,设计之初,这门语言就是定位为很简单的脚本语言,甚至连”类“都不愿意引入,怎么可能会考虑多重继承呢?)。所以,众多JS开发者引入了mixin来解决这个问题。
举个例子。如下面代码所示,有两个超类,
SuperType
和AnotherSuperType
,有一个子类SubType
。一开始SubType
继承于SuperType
,但是,现在我又想让SubType
实例化的某个对象obj也拥有超类AnotherSuperType
的方法,怎么办?看,这就是为了替代多重继承所引入的mixin。
异曲同工
纵观上面两派的观点,虽然各自要解决的问题和应用场景不尽相同,但是,有一处是相同的,那就是:mixin(混合)本质上是将一个对象的属性拷贝到另一个对象上面去,其实就是对象的融合。
这时候我们回过头来考察React和Vue中的mixin,就容易理解多了。而且,不仅是React和mixin,很多js库都有类似的功能,一般定义在util工具类中。有时候会换个名字,不叫mixin,叫
extend
,不过它们本质上是一样的。另外,说到对象融合,Object.assign也是常用的方法,它跟mixin有一个重大的区别在于:mixin会把原型链上的属性一并复制过去(因为for...in
),而Object.assign则不会。寄生式继承
什么?mixin居然也跟寄生式继承有关?
是的。正是结合mixin与工厂模式,才诞生了寄生式继承。
《高程》章节6.3中,介绍了很多种继承方式,其中我一直都记不住寄生式继承,因为我无法理解为什么会有这么一种继承方式(连名字都是怪怪的)。
下面来看这个例子。
仔细观察代码,我们能够发现寄生式继承的原理:
出处:《高程》章节6.3.5,第171页
PS,此处对寄生式继承进行阐述,并非代表我推荐使用这种继承模式,我只是希望通过结合mixin来帮助理解为什么会产生这种继承方式。恰恰相反,我从未在生产环境中用过这种(怪怪的)模式,我个人常用的还是经典的组合模式。
ES7装饰器
为什么我会由mixin联想到ES7装饰器呢?
因为我记得以前刚开始用React创建组件的时候,还是老的语法,
const demo = React.createClass
,这种情况下是可以用mixin的。后来React用ES6重写之后,就有了class demo extends React.Component
这样新的写法,这种情况就用不了mixin了。why?大概说来,就是ES6的class语法不支持,具体的可以参考这篇文章。也正是由此我发现了ES7有装饰器(decorator)这一功能。仔细看了一些,并未能完全掌握,加之decorator这东西太高级了,距离我生产环境太远,就先作罢,暂不深究。
然而,正是“装饰器”这一名字,让我想起了设计模式中有一种就叫装饰者模式,这东西就非常有实用价值了。
装饰者模式
接手别人的项目总是不可避免的(很可能是常见的),对于一些别人早就写好的函数,当我们想往里面添加一些功能的时候,该怎么办呢?
举个例子,我们的目标是在原有的函数
doSomething
里面的最后再输出一些其他的东西,下面是不好的(可能是常用的)的做法。可以看到,这种做法非常简单粗暴,我们直接修改了别人的函数,这违反了开放-封闭原则,并非正途。
下面是好一些的做法。
仔细分析代码,我们能发现其原理:用一个临时变量暂存原函数,然后定义一个新函数拓展原有的函数。这就是装饰者模式:为函数(对象)动态增加职责,又不直接修改这个函数(对象)本身。
然而,我们同时也能发现这个方案的缺点:需要增加一个临时变量来存储原函数。当需要装饰的函数越来越多的时候,临时变量的数量呈线性增长,代码将变得越来越难以维护。
怎么办?请看下面的例子。
这样,有了
before
和after
方法,我们就能在函数的前面和后面动态添加功能,而且非常的优雅。这一部分摘抄自曾探所著《JavaScript设计模式与开发实践》第二部分第15章,书中还列举了很多在业务上常见的情况,比如在已经写好的按钮点击事件上添加数据打点功能,比如把字段校验功能移到submit函数外边等等,这些例子都非常的生动实用,建议读者直接阅读原著。
总结
长长的思路到此终于结束,让我们回想一下:无论是复制类的mixin、多重继承的mixin、寄生式继承、ES7的装饰器、设计模式中的装饰者模式,它们都有一个共同点,那就是:在不修改原有的类/对象/函数的前提下,为新的类/对象/函数添加新的职责,以增强其功能。其实这些都是以下两个程序设计原则的外化:开发-封闭原则和单一职责原则。
The text was updated successfully, but these errors were encountered: