在现实世界中,一个女性可以具有不同的角色。她可以是同时是妈妈,职工,妻子。根据不同的角色,她需要做不同的行为。这就是多态的概念,只是用另一种形式表现。
在JS中,多态这个概念并不广为人知,但是在面向对象编程中,它是非常核心的一个概念。
面向数据的编程语言,比如Rust。也会用Entity Component System (ECS)实现多态。JS中的编程写法具有不同的模式。在这个主题里,我们将会揭示多态是什么,在JS中怎么使用,多态的不同类型。
什么是多态
假如我们要计算一个区域的面积和周长,我们可能会定义两个方法area()
和 perimeter()
。但是我们不可能用一套算法,来计算不同的形状。比如圆形和三角形的周长公式是不一样的。
那我们要先定义出基类 shapes,然后把不同的形状当做它的子类或派生类,他们具有各自的周长计算公式。这就叫多态。
在编程中,多态的定义是一个对象的能力具有多种模式。下一小节,我们会更加深入的理解JS是如何处理多态的。
JS如何处理多态
不同语言实现多态的方式有所不同。比如JS和JAVA都是面向对象的编程语言,但是实现多态的方式也不同。我们也会看到在封装和继承中多态是如何工作的。
多态和面向对象编程
面向对象模型依赖的概念是对象和类。通过this
或 self
来改变它的数据字段
我们会对比一下,用JS实现多态和其他面向数据编程实现多态有什么区别(使用ECS)。 使用面向对象编程,我们可以创建一个类来计算不同形状的面积和周长:
class Shape {
area() {
return 0;
}
perimeter() {
return 0;
}
toString() {
return Object.getPrototypeOf(this).constructor.name;
}
}
class Circle extends Shape {
constructor(r) {
super();
this.radius = r;
}
area() {
return Math.PI * this.radius ** 2;
}
perimeter() {
return Math.PI * this.radius * 2;
}
}
class Rectangle extends Shape {
constructor(w, h) {
super();
this.width = w;
this.height = h;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
function cumulateShapes(shapes) {
return shapes.reduce((sum, shape) => {
if (shape instanceof Shape) {
console.log(`Shape: ${shape.toString()} - area: ${shape.area()}`);
console.log(
`Shape: ${shape.toString()} - perimeter: ${shape.perimeter()}`
);
return sum + shape.area();
}
throw Error("Bad argument shape.");
}, 0);
}
const shapes = [new Circle(3), new Rectangle(2, 3)];
console.log(cumulateShapes(shapes));
如果使用ECS,那么会变成这样
var Position;
var Circle;
var Rectangle;
class CirlceSystem extends Position {
OnUpdate() {
ComponentQuery.SelectReadOnly(typeof Position, typeof Circle).ForEachEntity(
(Entity, Position, Circle) => {
/* find area and perimeter */
}
);
}
}
class RectangleSystem extends Position {
OnUpdate() {
ComponentQuery.SelectReadOnly(
typeof Position,
typeof Rectangle
).ForEachEntity((entity, Position, Rectangle) => {
/* find area and perimeter */
});
}
}
在JS代码中,我们使用了继承。在ECS代码中,我们使用了ECS模型来实体分发到组件中,解耦数据。
我们深入理解一下,JS中的继承,以及它如何跟多态相关
多态和继承
在面向对象的多态中,继承是非常重要的一个特性。
看一个car
的例子
class Car {
constructor(color, speed) {
this._speed = speed;
this._color = color;
}
}
现在我们有不同的子类了,比如宝马,丰田,本田等等,他们的属性比如颜色和速度有所不同
class BMW extends Car {
constructor(color, speed, make) {
super(color, speed);
this._make = make;
}
showInfo() {
console.log(
"I’m " +
this._make +
", my color is " +
this._color +
", and my speed is " +
this._speed
);
}
}
class Toyota extends Car {
constructor(color, speed, make) {
super(color, speed);
this._make = make;
}
showInfo() {
console.log(
"I’m " +
this._make +
", my color is " +
this._color +
", and my speed is " +
this._speed
);
}
}
class Bentely extends Car {
constructor(color, speed, make) {
super(color, speed);
this._make = make;
}
showInfo() {
console.log(
"I’m " +
this._make +
", my color is " +
this._color +
", and my speed is " +
this._speed
);
}
}
现在,我们给我们的车添加不同的颜色,速度。
var myBentely = new Bentely('Red', '20mph', 'Bentely');
var myBMW = new BMW('Green', '40mph', 'BMW');
var myToyota = new Toyota('White', '60mph', 'Toyota');
console.log(myBentely.showInfo());
console.log(myBMW.showInfo());
console.log(myToyota.showInfo());
在例子中,子类获取了父类的属性,并定义它。继承可以由当前的从父类甚至祖父类中派生。
JS种继承的类型
JS 继承时一个很大的话题,有很多不同的实现方式,比如基于原型的,类的(虚假的类),函数的。我们简单看一下区别,以及他们如何实现多态:
1. 原型继承 原型的比较简单,就在原型上加方法就可以了
let Car = {
color: "Red",
};
let BMW = {
make: "BMW",
};
BMW.__proto__ = Car;
// we can find both properties in BMW now:
console.log("This is a " + BMW.color + " " + BMW.make);
2. 类继承
前面说过,JS种的类时为伪概念,语法糖,所以我们称之为伪类。 class的实现是基于new关键字,但是调用的是一个函数。比如,我们有一个car对象。
function Car(make, color, speed) {
this.make = make;
this.color = color;
this.speed = speed;
}
我们可以使用new 关键字,来给他创建不同的子类。
var Toyota = new Car ("Toyota", "Red", "100mph");
var Bentley = new Car ("Bentley", "White", "120mph");
var BMW = new Car ("BMW", "Green", "90mph");
使用原型,我们创建了不同的car对象。下一步,我们会看一下如何像继承意向传递原型,以及这样做对多态有什么样的影响
首先,我们创建一个dialogue
函数,让我们的card继承它。
function dialogue() {
console.log('I am ' + this.make);
}
利用原型,让我们的cars来继承它
Car.prototype.dialogue = function () {
console.log(
"I am a " +
this.color +
" " +
this.make +
" with " +
this.speed +
" speed "
);
};
console.log(Toyota.dialogue());
console.log(BMW.dialogue());
console.log(Bentley.dialogue());
3. 基于函数的继承
基于函数的继承,是给对象加上增强函数
function Person(data) {
var that = {};
that.name = data.name;
return that;
}
// Create a child object, to inherit from the base Person
function Employee(data) {
var that = Person(data);
that.sayHello = function () {
return "Hello, I'm " + that.name;
};
return that;
}
var myEmployee = Employee({ name: "Rufi" });
console.log(myEmployee.sayHello());
多态和封装
理解了继承,再理解封装就很容易了。在写代码的时候,我们经常需要把一些代码封装起来,这样用户从外面就无法访问里面的值。
例如,我们把验证学生特征的数据组合在一起,然后使用基于原型多态的方式来继承。
function Student(name, marks) {
var student_name = name;
var student_marks = marks;
Object.defineProperty(this, "name", {
get: function () {
return student_name;
},
set: function (student_name) {
this.student_name = student_name;
},
});
Object.defineProperty(this, "marks", {
get: function () {
return student_marks;
},
set: function (student_marks) {
this.student_marks = student_marks;
},
});
}
var stud = new Student("Mercy's score is: ", 60);
console.log(stud.name + " " + stud.marks);
这个例子很好的帮助我们理解JS中的封装和多态。 很多人不理解抽象和封装的区别。抽象,只能看到一部分信息,其他的部分被隐藏了。而封装,是把数据包带一个单独的实体中,外界无法访问。使用封装最主要的原因是控制和校验数据--就像上面的例子一样。
多态的类型
JS中实现多态有多种方式,我们讨论以下几种
Ad-hoc Polymorphism(特设多态)
特设多态是指'视觉上'不同的类型,表现的行为也是不同的。特设多态可以包含同名的,但是参数或返回值不同的函数。
这种类型也被叫做重载,我们看一个操作符的重载。
Operator Overloading
5 + 5; // will print 10
'I am' + ' ' + '5 years old' // will print I am 5 years old
在上面的例子中,+
表示了数字相加以及字符串拼接两种范式。
参数化多态
参数化多态处理普通的函数和数据类型,同时维持静态类型安全。普通函数和数据类型,可以被重写,所以不会基于他们的类型进行区分对待。 例如,对象保存了不同的数据类型。它不会基于他们的类型来区分他们的值。
const Ann = {
firstName: 'Ann',
lastName: 'Kim',
Age: 4,
Adult: false
}
上面的Ann对象,包含了Ann的名字-字符串类型,年龄--数字类型,是否成年--布尔类型。尽管他们的类型不同,但是对于对象说,处理的方式是差不多的。
类似的例子还有数组。在JS中,数组可以承载不同的元素。
const Ann = [‘Ann’, ‘Kim’, 4, false];
数组对它包含的元素处理也是类似的,如果你在控制台运行console.log(Ann)
,能发现所有的元素都被打印出来。
看另外一个例子
const id = (x) => x;
id(1); // 1
id("foo"); // "foo"
id
不会因为参数1
and foo
的类型来判断他们的值。所以你可以给 id
传入不同类型的参数。
子类型多态
子类型多态包含子类型和子类型数据类型。它不会包含新对象的创建,主要基于接口的实现,以及不同的实现方式。
假如你获得了亲人的遗产---一个书店。那么你可以查阅里面的书,查阅遗产账户,书店客户等等,这叫做继承,你获得了遗产里所有的东西。
假如亲人这份遗产没有给你,你可以选择自己重新开一个,然后承担起你亲戚原来的角色,但是根据自己的喜欢做一些改变---这叫子类型。
看个例子
function Animal () {
}
Animal cat = new Cat ("Kitty");
Animal Dog = new Cat ("puppy");
Animal cat = new Cat ("Kiddy");
//you can go ahead to create different properties for different animals
cat ,dog,goat都是animals的子类型。一个animal可以是任何一个。你可以不同的animal做不同的实现。
常见的JS多态陷阱
我们大概的讲了一下多态,但是也要谨记一些陷阱:
- 多态会影响你代码的性能。一个单一的函数要比多态函数运行的快。
- 多态会降低代码的可阅读性。为了解决这个问题,所以在使用多态时写好注释。
- JS中实现多态很容易,但是要理解继承。因为JS中多态时围绕继承实现的。
为什么使用多态
为了复用。 一方面,因为要使用继承,提升了代码的复用能力。 另一方面,可以把不同类型的数据放在一起处理。比如我们熟悉的数组。
const Ann = [‘Ann’, ‘Kim’, 4, false];
程序中使用多态,最主要的还是让程序扩展性和维护性更好。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!