编码概念 - 泛型 什么是泛型?为什么要使用泛型? 附加阅读

2025-05-25

编码概念 - 泛型

什么是泛型?为什么我们要使用它们?

补充阅读

什么是泛型?为什么我们要使用它们?

泛型编程是一种计算机编程 风格  ,其中 算法以 稍后指定的 类型 编写 ,然后在需要时为作为参数提供的特定类型实例化。这种方法由ML 于 1973 年首创 , [1] [2] 允许编写通用 函数 或类型,这些函数或 类型 仅在使用时操作的类型集上有所不同,从而减少 重复。此类软件实体在 Ada、  C#、  Delphi、  Eiffel、  F#、  Java、  Rust、  Swift、  TypeScript 和 Visual Basic .NET中称为泛型。它们  在 ML、  Scala、  Haskell  (Haskell 社区也使用术语“泛型”来表示相关但略有不同的概念)和 Julia中称为参数多态性; 在C++ 和 D 中 称为模板;在具有影响力的 1994 年著作 《设计模式》中称为参数化类型。[3]  《设计模式》的作者指出,这种技术非常强大,尤其是与 委托相结合时,但是,动态的、高度参数化的软件比静态的软件更难理解。

所以,这是一个来自维基百科的冗长且缺乏描述性的定义。我对泛型着迷了一段时间,它们可能很难理解,很难理解为什么以及应该在何处使用它们。使用泛型的主要动机是在成员之间提供有意义的类型约束。泛型或参数多态性在大多数编程语言中都有使用,尽管它们可能更难理解,但使用它们主要有 5 个好处。

好处

  • 编译时进行更强的类型检查。
  • 修复编译时错误比修复运行时错误更容易
  • 消除演员阵容。这反过来会更快。
  • 使编码人员能够实现通用解决方案,这些解决方案可以重复用于多种用途。
  • 为未来的数据类型提供未来保障。

使用泛型比使用标准类型强大得多,它允许我们创建某种形式的封装和定义明确的一致 API。如何使用它们并不重要,重要的是了解如何在不同成员之间传递此类信息,以及如何在外部使用泛型并返回类型,这将为代码中发生的事情提供更具描述性的视图。

本质上,泛型只是意味着你可以将一个类型赋值给一个类。因此,在本例中我们会看到“T”。

为什么要使用泛型?为了抽象出数据类型,允许你重用代码并提高可维护性。

因此,让我们通过一个简单的 TypeScript 示例来展示我的意思。

标准类示例

我们将从通用列表开始,然后将其切换为通用列表!

class Stack
{
private stack: any[];
  pushItem(item){
  this.stack.push(item);
  }
}
Enter fullscreen mode Exit fullscreen mode

上面的示例是一个基础类,它包含一个名为 stack 的数组。任何东西都可以添加到这个数组中!让我们添加一个字符串、一个数字和一个新的 person 对象。


var newStack = Stack();
var aString = "A String";
var aNumber = 100;
var aPerson = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};
newStack.pushItem(aString);
newStack.pushItem(aNumber);
newStack.pushItem(aPerson);
Enter fullscreen mode Exit fullscreen mode

这确实可行,也许你想要一个可以容纳各种对象的数组。然而,在大多数情况下,这会在迭代数组、排序或过滤数组中的值时引发许多问题。最糟糕的是,你直到运行时才会发现这些错误。这些错误直到代码执行时才会被发现,因此在测试期间可能找不到。

而通用列表不允许您向堆栈添加它无法处理的类型。

要使用通用容器,您必须在实例化时使用尖括号表示法指定容器的类型。

其实很简单,我们先回顾一下上面的例子,但这次我们创建两个独立的数组实例,一个用来存储数字,一个用来存储字符串。首先,我们需要创建一个泛型类。

通用方法

t型杯的图片结果

class GenericStack<T>;
{
  private stack: T[]; 
  function pushItem(item: T) { 
  this.stack.push(item); 
  }
}
Enter fullscreen mode Exit fullscreen mode

正如你所见,这段代码和上面的示例几乎一模一样!那么,它到底有什么用呢?它能给我们带来什么好处呢?让我们先来看看初始化这个新类时的示例。

var numberStack = GenericStack<Number>(); 
var stringStack = GenericStack<String>(); 
var aString = "A String"; 
var aNumber = 100; 
var aPerson = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};

// These will pass the typescript compiler
stringStack.pushItem(aString); 
numberStack.pushItem(aNumber);

// But these would all fail.
numberStack.pushItem(aPerson);
numberStack.pushItem(aString);
stringStack.pushItem(aPerson);
stringStack.pushItem(aNumber);
Enter fullscreen mode Exit fullscreen mode

那么这一切意味着什么呢?本质上,我们只创建了一个类,但根据引用的类型改变了它的行为。这本质上是类和类型之间的契约。没错,我们可以创建两个独立的类,但那样的话,代码就会重复。

想象一下,如果我们创建一个 Person 类,而不仅仅是一个 JObject,我们可以创建一个GenericStack<Person>()

虽然这是一个很简单的例子,但类型安全非常重要,尤其是在 JavaScript 中。由于 JavaScript 不是编译型语言,Typescript 为我们提供了类型安全和预编译的优势,可以捕获此类错误。

如前所述,如果没有通用类,错误只会在运行时出现。

开发的主要部分是构建可重用的、定义明确的组件,如果此类包含更多功能,则整个组织的团队可以以最少的努力重复使用它,从而允许他们重用自己的类型。

但这是否限制了您使用泛型所能做的事情?

当你使用诸如字符串、数字甚至数组之类的原始类型时,你会熟悉某些可用的方法,例如:  .ToString() 或 .length() 或 .size() 或 .replace()

这些要求编译器知道变量的类型,不幸的是,使用泛型时,这意味着它们无法使用。数字类型不包含replace(), 因此您无法使用它。T类型也不包含上面列出的任何类型!很多人会尝试在代码中实现泛型,只是为了表示他们使用了它们。需要确保的是,它们确实有实际的用例。当您开始不再使用原始数据类型(可用的基本数据类型:数字、字符串等)而使用自定义对象和类时,泛型就会发挥作用。

重构

泛型在重构代码时很有用,您是否可以在代码中看到可以从数据结构中抽象出数据类型的实例?

如果答案是肯定的 ,那么您应该考虑仿制药!

关于泛型还有很多内容,我不会在这里尝试解释所有内容,如果您有兴趣阅读有关泛型的更多信息,我在下面链接了其他资源,这些资源应该可以描绘出更清晰的画面。

我是不是漏掉了什么有用的东西?你还有什么要补充的吗?你有没有用过一些有趣的泛型?如果有的话,请在下方分享! 

谢谢阅读。

克里斯

补充阅读

官方 TypeScript 文档 - 泛型

Dzone - 了解泛型的用例

Git 书籍 - 泛型

Code.tutsplus. - 教程 - Typescript 初学者

 

文章来源:https://dev.to/chris_bertrand/coding-concepts---generics-34cf
PREV
确定要离开吗?——浏览器 beforeunload 事件
NEXT
我终于明白了什么是类 Chris 类 Daniel 类 我们得到了什么好处?结语 额外提示