在前一篇中我们介绍了基础的 JavaScript 继承实践法,透过 Object.prototype 我们可以自由决定对象要继承自哪个对象,也可以扩充对象目前现有的属性和方法 (和 C# 的 Extension Method 有异曲同工之妙),在本篇中,我们要来介绍面向对象的另一个特性:多态 (Polymorphism)。
在前一篇中我们介绍了基础的 JavaScript 继承实践法,透过 Object.prototype 我们可以自由决定对象要继承自哪个对象,也可以扩充对象目前现有的属性和方法 (和 C# 的 Extension Method 有异曲同工之妙),在本篇中,我们要来介绍面向对象的另一个特性:多态 (Polymorphism)。
多态的定义是,相同的行为 (behavior),在不同的对象上会有不同的反应。最常被举的例子就是动物或车子了,例如我有一个 Car 对象,里面有一个 getName() 方法,而我们定义了 HondaCRV 和 ToyotaWish 两个对象来继承它,其完整定义为:
1: function Car() {
2:
3: this.name = "BASE";
4:
5: this.getName = function () { return this.name; };
6: this.drive = function () { document.write("Drive BASE
"); };
7:
8: }
9:
10: function HondaCRV() {
11: HondaCRV.prototype.name = "HONDA CRV";
12: }
13:
14: HondaCRV.prototype = new Car();
15:
16: function ToyotaWish() {
17: ToyotaWish.prototype.name = "TOYOTA Wish";
18: }
19:
20: ToyotaWish.prototype = new Car();
然后在主程序这样调用:
1: function init() {
2:
3: document.write((new Car()).getName() + "
");
4: document.write((new HondaCRV()).getName() + "
");
5: document.write((new ToyotaWish()).getName() + "
");
6:
7: }
可得到这样的结果:
这个范例程序就是典型的多态运用,虽然它共用了父类的变量,不过我们还可以再进一步做多态的能力。在 Car 中有一个方法 drive(),现在我们要在 HondaCRV 和 ToyotaWish 中覆写它,程序如下:
1: function Car() {
2:
3: this.name = "BASE";
4:
5: this.getName = function () { return this.name; };
6: this.drive = function () { document.write("Drive BASE
"); };
7:
8: }
9:
10: function HondaCRV() {
11: HondaCRV.prototype.name = "HONDA CRV";
12: HondaCRV.prototype.drive = function () {
13: document.write("Drive HONDA CRV now.
");
14: };
15: }
16:
17: HondaCRV.prototype = new Car();
18:
19: function ToyotaWish() {
20: ToyotaWish.prototype.name = "TOYOTA Wish";
21: ToyotaWish.prototype.drive = function () {
22: document.write("Drive TOYOTA Wish now.
");
23: };
24: }
25:
26: ToyotaWish.prototype = new Car();
请注意,我们现在使用了 Object.prototype.[method] 的方式来覆写父对象的方法,以执行对象自己的动作。然后修改主程序:
1: function init() {
2:
3: document.write((new Car()).getName() + "
");
4: document.write((new HondaCRV()).getName() + "
");
5: document.write((new ToyotaWish()).getName() + "
");
6:
7: (new Car()).drive();
8: (new HondaCRV()).drive();
9: (new ToyotaWish()).drive();
10:
11: }
执行它,我们可以得到下面的结果:
到了这里,我想你应该了解如何使用 JavaScript 实践多态的功能了,接着我们就要来做和多态有高度相关的功能:界面 (interface)。
有写过 C#/Java/VB 这种面向对象语言程序的人应该都知道,界面是一种合约 (contract),它具有很强的强制性,只要是有参考界面但未实践的话会被掷回编译错误,所以不用担心界面没有被实践,然而 JavaScript 是一种类型松散的直译式语言,没办法强制执行这种检查,所以这部分我们得自己做,不过 JavaScript 有些方便的辅助对象,可以帮我们解决一些事情。
例如,我们订了一个 IRateCalculator 界面,里面有一个 getAmount() 方法:
1: function IRateCalculator() {
2: // contract method.
3: this.getAmount = function (amount) { throw "ERROR_INTERFACE_METHOD_MUST_BE_IMPLEMENTED"; };
4: }
然后我们定义了两个对象 SavingCalculator 与 LoanCalculator,皆实践 IRateCalculator 界面,定义自己的 getAmount() 方法:
1: SavingCalculator.prototype = new IRateCalculator();
2:
3: function SavingCalculator(amount) {
4:
5: this.amount = amount;
6:
7: SavingCalculator.prototype.getAmount = function (amount) {
8: return amount * 1.01; // 1%
9: };
10:
11: }
12:
13: LoanCalculator.prototype = new IRateCalculator();
14:
15: function LoanCalculator(amount) {
16:
17: this.amount = amount;
18:
19: LoanCalculator.prototype.getAmount = function (amount) {
20: return amount * 1.20; // 20%
21: };
22:
23: }
在主程序中我们要使用 SavingCalculator 和 LoanCalculator 计算十万元的本利和:
1: function init() {
2:
3: var saving = new SavingCalculator();
4: var loan = new LoanCalculator();
5:
6: // check interface.
7: console.log(IRateCalculator.prototype);
8: console.log(SavingCalculator.prototype);
9: console.log(typeof SavingCalculator.prototype.getAmount);
10:
11: if (IRateCalculator.prototype.isPrototypeOf(saving))
12: document.write("Saving's Rate Amount of 100000 is: " + saving.getAmount(100000) + "
");
13: else
14: document.write("Your code is not implement IRateCalculator interface.");
15:
16: if (IRateCalculator.prototype.isPrototypeOf(loan))
17: document.write("Loan's Rate Amount of 100000 is: " + loan.getAmount(100000) + "
");
18: else
19: document.write("Your code is not implement IRateCalculator interface.");
20:
21: }
执行结果为:
看起来很平常吧,但其实问题有两个:
1. 主程序必须要确认对象有实践 IRateCalculator 界面。
2. 主程序必须检查对象确实实践了 IRateCalculator.getAmount() 方法。
针对第一个问题,我们可以利用 Object.prototype.isPrototypeOf() 方法来确认,它可以检查界面是否被某对象实践,因此我们检查了这件事,并决定是否要调用 getAmount() 方法。然而第二个问题仍无解,因为 Object.prototype 无法回传更多的类型资讯,因此无法得到确实的类型比对依据,无法很确定 SavingCalculator 和 LoanCalculator 的 getAmount() 确实就是 IRateCalculator.getAmount() 方法,只能够暂时信任对象实践的确实是界面所定义的方法。
善用多态,可以创造出很多不同的 JavaScript 对象应用,而且它也能做为 Design Pattern 的入口基石,让编写可高度重复使用的 JavaScript 程序能更加容易。
原文:大专栏 [JavaScript] JavaScript 面向对象设计 (3) : 多态与界面篇