"泛型", 计算机编程中, 一个必不可少的概念。
简单理解泛型
什么是泛型
泛型是程序设计语言的一种特性。通过参数化类型来实现在同一份代码上操作多种数据类型。
对于强类型语言来书, 提到参数,最熟悉不过的就是定义 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") // trueareIntEqual(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(value: T): T { console.log(value); return value;}var sValue = middleware(service.getStringValue());var nValue = middleware(service.getNumberValue());复制代码
middleware 后面紧接的 表示声明一个表示类型的变量,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 表达式提供
// 声明泛型类,类型变量为 Tclass FilteredList{ // 声明过滤器是以 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 类型的 FilteredListconst validStrings = new FilteredList (s => !s);// 处理 number 类型的 FilteredListconst positiveNumber = new FilteredList (n => n > 0);复制代码
甚至还可以把 (v: T) => boolean 声明为一个类型,以便复用:
type Predicate= (v: T) => boolean;class FilteredList { filter: Predicate ; data: T[]; constructor(filter: Predicate ) { ... } add(value: T) { ... } get all(): T[] { ... }}复制代码