javaScript设计模式-Mixin模式

Mixin模式

在C++和Lisp等传统编程语言中,Mixin是可以轻松被一个子类或一组子类继承功能的类,目的是函数复用。

子类化

子类化这个术语是指针对一个新对象,从一个基础或超类对象中继承相关的属性。在传统的面向对象编程中,类B是从另外一个类A扩展得来。这里我们认为A是一个超类,B是A的一个子类。因此,B的所有实例从A处继承了相关方法。但是B仍然能够定义自己的方法,包括那些A最初定义方法的重写。

A中的一个方法,在B中已经被重写了,那么B还需要调用A中的这个方法吗,我们称此为方法链。B需要调用构造函数A吗,我们称此为构造函数链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Person = function(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = 'male';
}

var clark = new Person('Clark', "Blue");

var Superhero = function(firstName, lastName, powers) {
Person.call(this, firstName, lastName);
this.powers = powers;
};

Superhero.prototype = Object.create(Person.prototype);
var superman = new Superhero('Clark', 'Blue', ["flight", "heat-vision"]);
console.log(superman)

Mixin(混入)

在Javascript中,我们可以将继承Mixin看作为一种通过扩展收集功能的方式。我们定义的每个新对象都有一个原型,可以从中继承更多属性。原型可以继承于其他对象的原型,但更重要的是,它可以为任意数量的对象实例定义属性。可以利用者一点来促进函数复用。

我们在标准对象字面量中定义一个包含实用函数的Mixin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var myMixins = {
moveUp: function() {
console.log('move up');
},

moveDown: function() {
console.log('move down');
},

stop: function() {
console.log('stop! in the name of love');
}
};

function carAnimator() {
this.moveLeft = function() {
console.log('move left');
};
}

function personAnimator() {
this.moveRandomly = function() {
console.log('move randomly')
};
}

_.extend(carAnimator.prototype, myMixins);
_.extend(personAnimator.prototype, myMixins);

var myAnimator = new carAnimator();
myAnimator.moveLeft(); // move left
myAnimator.moveDown(); // move down
myAnimator.stop(); // stop! in the name of love
var myPersonAnimator = new personAnimator();
myPersonAnimator.moveRandomly(); // move left
myPersonAnimator.moveDown();// move down
myPersonAnimator.stop();// stop! in the name of love

正如我们所看到的,这允许我们以通用的方式轻松“混入”对象构造函数

在下一个示例中,我们有两个构造函数:Car和Mixin。我们要做的是扩充(扩展的另一种说法)Car,以便它可以继承Mixin中定义的特定方法,即driveForward()和driveBackward()。这次我们不会实用underscore.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
var Car = function(settings) {
this.model = settings.model || 'no model provided';
this.color = settings.color || 'no clolor provided';

}

// Mixin
var Mixin = function(){};

Mixin.prototype = {
driveForward: function() {
console.log('drive forward');
},

driveBackward: function() {
console.log('drive backward');
},

driveSideways: function() {
console.log('drive sideways');
}
};

// 通过这个方法将现有的对象扩展到另外一个对象上
function augment(receivingClass, givingClass) {
// 如果给了指定混合的属性名
if(arguments[2]) {
// 把givingClass-->Mixin的属性给到receivingClass-->Car对象
for(var i = 2, len = arguments.length; i < len; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
} else { // 默认混合所有的属性名
for(var methodName in givingClass.prototype) {
// 只混合自定义的属性
if(!Object.hasOwnProperty(receivingClass.prototype, methodName)) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
// 另一种方式
// if(!receivingClass.prototype[methodName]) {
// receivingClass.prototype[methodName] = givingClass.prototype[methodName];
// }
}
}
}

// 给Car构造函数增加"driveForward"和"driveBackward"两个方法
augment(Car, Mixin, "driveForward", "driveBackward");

// 创建一个新Car
var myCar = new Car({
model: 'Ford Escort',
color: 'blue',
});

// 测试确保新增方法可用
myCar.driveForward();
myCar.driveBackward();

// 也可以通过不声明特定方法名的形式,将Mixin的所有方法都添加到Car里
augment(Car, Mixin);

var mySportCar = new Car({
model: 'Porsche',
color: 'red',
});

mySportCar.driveSideways();

优点和缺点

Mixin有助于减少系统种的重复功能及增加函数复用。当一个应用程序可能需要在各种对象实例中共享行为时,我们可以通过在Mixin中维持这种共享功能并专注于仅实现系统中真正不同的功能,来轻松避免任何重复。

有些开发人员认为将功能注入对象原型是一种很糟糕的想法,因为它会导致原型污染和函数起源方面的不确定性。在大型系统中,可能就会有这种情况。