“泛型”, 计算机编程中, 一个必不可少的概念。
简单理解泛型
什么是泛型
泛型是程序设计语言的一种特性。通过参数化类型来实现在同一份代码上操作多种数据类型。
对于强类型语言来书, 提到参数,最熟悉不过的就是定义 function A 时有形参,然后调用 A 时传递实参。 指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后通过实际调用时传入类型 来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。
注: 各种程序设计语言和其编译器、运行环境对泛型的支持均不一样
泛型解决的问题
- 可重用性
- 类型和算法安全
- 效率
这是非泛型类和非泛型方法无法具备的
常见的情形
你有一个函数,它带有一个参数,参数类型是A,然而当参数类型改变成B的时候,你不得不复制这个函数
例如,下面的代码中第二个函数就是复制第一个函数——它仅仅是用String类型代替了Integer类型
func areIntEqual(x: Int, _ y: Int) -> Bool {
return x == y
}
func areStringsEqual(x: String, _ y: String) -> Bool {
return x == y
}
areStringsEqual("ray", "ray") // true
areIntEqual(1, 1) // true
通过采用泛型,可以合并这两个函数为一个并同时保持类型安全。下面是代码实现
// 用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替
func areTheyEqual(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
JavaScript和泛型的对应关系
泛型 和 模板方法(设计)模式
在一个系列的行为中,有一些是确定的,有一些是不明确的,我们把确定的行为定义在一个抽象类中, 不确定的行为定义为抽象方法,由具体的子类去实现,这种不影响整个流程,但可以应对各种情况的方法 就可以称之为模板方法模式
demo - Coffee or Tea
几个步骤:
- 把水煮沸
- 用沸水浸泡茶叶
- 把茶水倒进杯子
- 加柠檬
/* 抽象父类:饮料 */
var Beverage = function(){};
Beverage.prototype.boilWater = function() {
console.log("把水煮沸");
};
Beverage.prototype.brew = function() {
throw new Error("子类必须重写brew方法");
};
Beverage.prototype.pourInCup = function() {
throw new Error("子类必须重写pourInCup方法");
};
Beverage.prototype.addCondiments = function() {
throw new Error("子类必须重写addCondiments方法");
};
/* 模板方法 */
Beverage.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
/* ------------分割线------------ */
/* 实现子类 Coffee*/
var Coffee = function(){};
Coffee.prototype = new Beverage();
// 重写非公有方法
Coffee.prototype.brew = function() {
console.log("用沸水冲泡咖啡");
};
Coffee.prototype.pourInCup = function() {
console.log("把咖啡倒进杯子");
};
Coffee.prototype.addCondiments = function() {
console.log("加牛奶");
};
var coffee = new Coffee();
coffee.init();
/* 实现子类 Tea*/
var Tea = function(){};
Tea.prototype = new Beverage();
// 重写非公有方法
Tea.prototype.brew = function() {
console.log("用沸水冲泡茶叶");
};
Tea.prototype.pourInCup = function() {
console.log("把茶倒进杯子");
};
Tea.prototype.addCondiments = function() {
console.log("加柠檬");
};
var tea = new Tea();
tea.init();
这里的Beverage.prototype.init就是所谓的模板方法
它作为一个算法的模板指导子类以何种顺序去执行哪些方法,在其内部,算法内的每一 个步骤都清楚的展示在我们眼前
泛型 和 TypeScript
- 泛型函数
- 泛型类
TypeScript 为 JavaScriopt 带来了强类型特性,但这就意味着限制了类型的自由度。同一段程序, 为了适应不同的类型,就可能需要写不同的处理函数
而且这些处理函数中所有逻辑完全相同,唯一不同的就是类型——这严重违反抽象和复用代码的原则
泛型函数
js源码
var service = {
getStringValue: function() {
return "a string value";
},
getNumberValue: function() {
return 20;
}
};
function middleware(value) {
console.log(value);
return value;
}
var sValue = middleware(service.getStringValue());
var nValue = middleware(service.getNumberValue());
ts改写使用泛型
const service = {
getStringValue(): string {
return "a string value";
},
getNumberValue(): number {
return 20;
}
};
// 泛型方法改造
function middleware<T>(value: T): T {
console.log(value);
return value;
}
var sValue = middleware(service.getStringValue());
var nValue = middleware(service.getNumberValue());
middleware 后面紧接的<T>
表示声明一个表示类型的变量,Value: T 表示声明参数是 T 类型的,
后面的 : T 表示返回值也是 T 类型的
到这里为止, TS改造之后的泛型方法和改造之前的js代码没什么区别。 现在的问题是 middleware 要怎么样定义才既可能返回 string,又可能返回 number,而且还能被类型检查正确推导出来? 如果不使用泛型方法要实现这个功能的代码实现:
第 1 个办法,用 any:
function middleware(value: any): any {
console.log(value);
return value;
}
这个办法可以检查通过。但它的问题在于 middleware 内部失去了类型检查,在后在对 sValue 和 nValue 赋值的时候, 也只是当作类型没有问题。简单的说,是有“假装”没问题
第 2 个办法,多个 middleware:
function middleware1(value: string): string { ... }
function middleware2(value: number): number { ... }
或者用 TypeScript 的重载(overload)来实现
function middleware(value: string): string;
function middleware(value: number): number;
function middleware(value: any): any {
// 实现一样没有严格的类型检查
}
这种方法最主要的一个问题是……如果我有 10 种类型的数据,就需要定义 10 个函数(或重载), 那 20 个,200 个呢……
泛型类
即在声明类的时候声明泛型,那么在类的整个作用域范围内都可以使用声明的泛型类型, 多数 时候是应用于容器类
背景: 假设我们需要实现一个 FilteredList,我们可以向其中 add()(添加) 任意数据, 但是它在添加的时候会自动过滤掉不符合条件的一些,最终通过 get all() 输出所有符合条件的数据(数组)。 而过滤条件在构造对象的时候,以函数或 Lambda 表达式提供
// 声明泛型类,类型变量为 T
class FilteredList<T> {
// 声明过滤器是以 T 为参数类型,返回 boolean 的函数表达式
filter: (v: T) => boolean;
data: T[];
constructor(filter: (v: T) => boolean) {
this.filter = filter;
}
add(value: T) {
if (this.filter(value)) {
this.data.push(value);
}
}
get all(): T[] {
return this.data;
}
}
// 处理 string 类型的 FilteredList
const validStrings = new FilteredList<string>(s => !s);
// 处理 number 类型的 FilteredList
const positiveNumber = new FilteredList<number>(n => n > 0);
甚至还可以把 (v: T) => boolean 声明为一个类型,以便复用:
type Predicate<T> = (v: T) => boolean;
class FilteredList<T> {
filter: Predicate<T>;
data: T[];
constructor(filter: Predicate<T>) { ... }
add(value: T) { ... }
get all(): T[] { ... }
}
最后, 希望大家早日实现:成为编程高手的伟大梦想!
欢迎交流~
本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!