Day 12 - 泛型与阶段三总结

目标:掌握ArkTS泛型编程,完成Phase 03 OOP知识总结
预计时间:2-2.5小时
前置知识:Day 09-11 所有OOP内容


第一部分:泛型函数(对标 C++ 函数模板)

1.1 为什么需要泛型

思考一个问题:如何编写一个返回数组最后一个元素的函数?

// 方案1:为每种类型写单独的函数
function getLastNumber(arr: number[]): number {
  return arr[arr.length - 1];
}

function getLastString(arr: string[]): string {
  return arr[arr.length - 1];
}

function getLastBoolean(arr: boolean[]): boolean {
  return arr[arr.length - 1];
}

// 问题:代码重复,维护困难

C++ 的解决方案是模板

template<typename T>
T getLast(std::vector<T>& arr) {
    return arr.back();
}

ArkTS 的解决方案是泛型

function getLast<T>(arr: T[]): T {
  return arr[arr.length - 1];
}

1.2 基本泛型函数语法

泛型函数使用 <T> 声明类型参数,函数体内可以像使用普通类型一样使用 T

// 泛型函数的基本语法
function identity<T>(arg: T): T {
  return arg;
}

// 使用
let num: number = identity<number>(42);
let str: string = identity<string>("hello");

对比C++函数模板

特性 C++ ArkTS
语法 template<typename T> <T>
类型参数位置 函数前 函数名后
实例化 编译期 编译期
类型推断 支持 支持
// C++ 函数模板
template<typename T>
T identity(T arg) {
    return arg;
}

// 使用
int num = identity<int>(42);
std::string str = identity<std::string>("hello");

1.3 类型推断

ArkTS 编译器可以根据传入的参数自动推断类型参数,通常不需要显式指定:

function swap<T>(a: T, b: T): T[] {
  return [b, a];
}

// 类型推断:编译器自动推断 T 为 number
let result1 = swap(10, 20);

// 类型推断:编译器自动推断 T 为 string
let result2 = swap("hello", "world");

// 显式指定(当推断不明确时需要)
let result3 = swap<number>(100, 200);

C++ 对比

template<typename T>
std::pair<T, T> swap(T a, T b) {
    return {b, a};
}

// C++17 起支持 CTAD(类模板参数推导),函数模板一直支持推断
auto result1 = swap(10, 20);        // T 推断为 int
auto result2 = swap(std::string("hello"), std::string("world"));

1.4 显式指定类型参数

某些情况下,编译器无法推断类型,需要显式指定:

function createArray<T>(length: number, value: T): T[] {
  let arr: T[] = [];
  for (let i: number = 0; i < length; i++) {
    arr.push(value);
  }
  return arr;
}

// 传入 null,T 被推断为 null 类型,可能不是预期结果
let arr1 = createArray(3, null);  // 类型为 null[]

// 显式指定 T 为 string
let strArray: string[] = createArray<string>(3, "default");

// 显式指定 T 为 number
let numArray: number[] = createArray<number>(5, 0);

1.5 多个泛型参数

泛型函数可以声明多个类型参数:

function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 使用
let p1 = pair<number, string>(1, "one");
let p2 = pair<string, boolean>("active", true);

// 类型推断
let p3 = pair(100, "hundred"); // T=number, U=string

对比C++

template<typename T, typename U>
std::pair<T, U> makePair(T first, U second) {
    return {first, second};
}

auto p1 = makePair(1, std::string("one"));
auto p2 = makePair(std::string("active"), true);

第二部分:泛型约束(对标 C++ concepts / SFINAE)

2.1 为什么需要约束

无约束的泛型可以传入任何类型,但有时候我们需要访问特定属性:

function logLength<T>(arg: T): void {
  // 编译错误:T 可能没有 length 属性
  // console.log(arg.length.toString());
}

// 调用时可能传入没有 length 的类型
logLength(42); // number 没有 length
logLength(true); // boolean 没有 length

问题:如何让泛型只接受有 length 属性的类型?

C++ 的解决方案

// C++20 concepts
template<typename T>
concept HasLength = requires(T t) {
    { t.length } -> std::convertible_to<int>;
};

template<HasLength T>
void logLength(T arg) {
    std::cout << arg.length << std::endl;
}

// C++11/14/17 使用 SFINAE
template<typename T>
typename std::enable_if<has_length<T>::value, void>::type
logLength(T arg) { ... }

2.2 extends 约束

ArkTS 使用 extends 关键字约束泛型参数必须满足特定接口:

// 定义约束接口
interface HasLength {
length: number;
}

// 泛型约束:T 必须实现 HasLength 接口
function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length.toString()); // 现在可以安全访问 length
}

// 使用
logLength("hello"); // string 有 length
logLength([1, 2, 3]); // 数组有 length
logLength({ length: 10 }); // 对象有 length 属性

// logLength(42);             // 编译错误:number 不满足约束
// logLength(true);           // 编译错误:boolean 不满足约束

对比C++ concepts

特性 C++20 ArkTS
约束定义 concept interface
应用约束 template<Concept T> <T extends Interface>
检查时机 编译期 编译期

2.3 使用 interface 定义约束

泛型约束通常与 interface 配合使用:

// 定义可比较接口
interface Comparable {
compareTo(other: Comparable): number;
}

// 约束:T 必须是可比较的
function findMax<T extends Comparable>(items: T[]): T {
  if (items.length === 0) {
    throw new Error("空数组");
  }

  let max: T = items[0];
  for (let i: number = 1; i < items.length; i++) {
    if (items[i].compareTo(max) > 0) {
      max = items[i];
    }
  }
  return max;
}

// 实现 Comparable 的类
class Device implements Comparable {
  id: number;
  name: string;

  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }

  compareTo(other: Comparable): number {
    // 类型断言,因为我们知道 other 也是 Device
    let otherDevice = other as Device;
    return this.id - otherDevice.id;
  }
}

// 使用
let devices: Device[] = [
  new Device(100, "设备A"),
  new Device(200, "设备B"),
  new Device(50, "设备C")
];
let maxDevice = findMax(devices);
console.log(maxDevice.name); // "设备B"

2.4 多重约束的实现

重要:ArkTS 不支持交叉类型(A & B),也不支持 <T extends A & B> 这样的语法。

如果需要多重约束,应该通过接口继承来实现:

// 定义基础约束接口
interface Identifiable {
id: number;
}

interface Named {
name: string;
}

// 通过接口继承合并约束
interface IdentifiableAndNamed extends Identifiable, Named {
// 继承了两个接口的所有属性
}

// 使用合并后的接口作为约束
function process<T extends IdentifiableAndNamed>(item: T): string {
  return `ID: ${item.id}, Name: ${item.name}`;
}

// 实现类
class Product implements IdentifiableAndNamed {
  id: number;
  name: string;
  price: number;

  constructor(id: number, name: string, price: number) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

// 使用
let product = new Product(1, "手机", 2999);
console.log(process(product));

更复杂的约束层次

// 基础能力接口
interface Loggable {
log(): void;
}

interface Serializable {
serialize(): string;
}

interface Validatable {
validate(): boolean;
}

// 合并多个约束
interface Entity extends Identifiable, Named, Loggable, Serializable {
createdAt: number;
}

// 使用
function saveEntity<T extends Entity>(entity: T): void {
  if (entity.validate === undefined || entity.validate()) {
    let data: string = entity.serialize();
    console.log(`保存 ${entity.name}(${entity.id}): ${data}`);
    entity.log();
  }
}

第三部分:泛型类(对标 C++ 类模板)

3.1 基本泛型类语法

泛型类在类名后使用 <T> 声明类型参数:

// 泛型类:类似 C++ 的 template<class T> class Box {}
class Box<T> {
  private content: T;

  constructor(value: T) {
    this.content = value;
  }

  getContent(): T {
    return this.content;
  }

  setContent(value: T): void {
    this.content = value;
  }
}

// 使用
let numberBox = new Box<number>(42);
console.log(numberBox.getContent().toString()); // "42"

let stringBox = new Box<string>("hello");
console.log(stringBox.getContent()); // "hello"

对比C++类模板

template<typename T>
class Box {
private:
    T content;
public:
    Box(T value) : content(value) {}
    
    T getContent() { return content; }
    void setContent(T value) { content = value; }
};

// 使用
Box<int> numberBox(42);
Box<std::string> stringBox("hello");

3.2 泛型类实例化

// 显式指定类型参数
let intBox = new Box<number>(100);
let strBox = new Box<string>("text");

// 类型推断(从构造函数参数推断)
let inferredBox = new Box(3.14); // 推断为 Box<number>

// 复杂类型
interface Device {
id: number;
name: string;
}

let deviceBox = new Box<Device>({ id: 1, name: "传感器" });
console.log(deviceBox.getContent().name);

3.3 泛型属性和方法

class GenericStack<T> {
  private items: T[] = [];
  private capacity: number;

  constructor(capacity: number) {
    this.capacity = capacity;
  }

  // 泛型方法使用类的类型参数
  push(item: T): boolean {
    if (this.items.length >= this.capacity) {
      console.log("栈已满");
      return false;
    }
    this.items.push(item);
    return true;
  }

  pop(): T {
    if (this.items.length === 0) {
      throw new Error("栈为空");
    }
    return this.items.pop() as T;
  }

  peek(): T {
    if (this.items.length === 0) {
      throw new Error("栈为空");
    }
    return this.items[this.items.length - 1];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }

  size(): number {
    return this.items.length;
  }
}

// 使用
let numStack = new GenericStack<number>(5);
numStack.push(10);
numStack.push(20);
numStack.push(30);
console.log(numStack.pop().toString()); // "30"

let strStack = new GenericStack<string>(3);
strStack.push("first");
strStack.push("second");
console.log(strStack.peek()); // "second"

对比C++

template<typename T>
class Stack {
private:
    std::vector<T> items;
    size_t capacity;
public:
    Stack(size_t cap) : capacity(cap) {}
    
    bool push(const T& item) {
        if (items.size() >= capacity) return false;
        items.push_back(item);
        return true;
    }
    
    T pop() {
        if (items.empty()) throw std::runtime_error("Empty");
        T item = items.back();
        items.pop_back();
        return item;
    }
    // ...
};

第四部分:泛型接口

4.1 泛型接口定义

接口也可以使用泛型:

// 泛型接口
interface Container<T> {
add(item: T): void;
remove(): T;
getSize(): number;
isEmpty(): boolean;
}

// 类实现泛型接口(指定具体类型)
class NumberContainer implements Container<number> {
  private items: number[] = [];

  add(item: number): void {
    this.items.push(item);
  }

  remove(): number {
    if (this.items.length === 0) {
      throw new Error("容器为空");
    }
    return this.items.pop() as number;
  }

  getSize(): number {
    return this.items.length;
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

// 使用
let numContainer = new NumberContainer();
numContainer.add(100);
numContainer.add(200);
console.log(numContainer.remove().toString()); // "200"

4.2 类实现泛型接口(保持泛型)

类可以实现泛型接口并保持类型参数:

// 泛型接口
interface Repository<T> {
findById(id: number): T;
findAll(): T[];
save(item: T): void;
delete(id: number): boolean;
}

// 泛型类实现泛型接口
class MemoryRepository<T> implements Repository<T> {
  private items: Map<number, T> = new Map();
  private nextId: number = 1;

  findById(id: number): T {
    let item = this.items.get(id);
    if (item === undefined) {
      throw new Error(`未找到ID: ${id}`);
    }
    return item;
  }

  findAll(): T[] {
    let result: T[] = [];
    this.items.forEach((value: T, key: number) => {
      result.push(value);
    });
    return result;
  }

  save(item: T): number {
    let id = this.nextId++;
    this.items.set(id, item);
    return id;
  }

  delete(id: number): boolean {
    return this.items.delete(id);
  }
}

// 定义实体接口
interface User {
name: string;
email: string;
}

// 使用
let userRepo = new MemoryRepository<User>();
let userId = userRepo.save({ name: "张三", email: "zhangsan@example.com" });
let user = userRepo.findById(userId);
console.log(user.name); // "张三"

4.3 泛型接口的继承

泛型接口可以继承其他泛型接口:

// 基础泛型接口
interface Readable<T> {
read(): T;
}

interface Writable<T> {
write(item: T): void;
}

// 继承多个泛型接口
interface ReadWrite<T> extends Readable<T>, Writable<T> {
clear(): void;
}

// 实现
class Buffer<T> implements ReadWrite<T> {
  private data: T[] = [];

  read(): T {
    if (this.data.length === 0) {
      throw new Error("缓冲区为空");
    }
    return this.data.shift() as T;
  }

  write(item: T): void {
    this.data.push(item);
  }

  clear(): void {
    this.data = [];
  }
}

// 使用
let buffer = new Buffer<string>();
buffer.write("message1");
buffer.write("message2");
console.log(buffer.read()); // "message1"

泛型接口继承时类型参数的独立性

interface A<T> {
a: T;
}

interface B<U> {
b: U;
}

// 继承时可以独立指定类型参数
interface AB<T, U> extends A<T>, B<U> {
// 继承 A 的 a: T 和 B 的 b: U
}

class ABImpl<T, U> implements AB<T, U> {
  a: T;
  b: U;

  constructor(a: T, b: U) {
    this.a = a;
    this.b = b;
  }
}

// 使用
let a = new ABImpl(1, 2);      // T=number, U=number
let b = new ABImpl(1, "2");    // T=number, U=string

第五部分:泛型默认参数与实用模式

5.1 泛型默认参数

泛型可以指定默认类型:

// 默认类型为 string
class Logger<T = string> {
  private prefix: T;

  constructor(prefix: T) {
    this.prefix = prefix;
  }

  log(message: string): void {
    console.log(`${this.prefix}: ${message}`);
  }
}

// 使用默认类型(T = string)
let defaultLogger = new Logger("INFO");
defaultLogger.log("系统启动"); // "INFO: 系统启动"

// 显式指定类型
let numberLogger = new Logger<number>(1001);
numberLogger.log("设备连接"); // "1001: 设备连接"

泛型默认类型的真实作用

  1. 简化复杂泛型的使用(减少需显式指定的类型参数数量)

    // 多个类型参数时,后面的可以使用默认值
    interface Container<T, U = T> {
        primary: T;
        secondary: U;
    }
    // 只指定一个类型,U 默认等于 T
    let c1: Container<number>;  // U 默认为 number
    
  2. 实现向后兼容(旧代码无需修改即可继续使用)

    // 给已有类型添加泛型,但保持向后兼容
    class Component<T = Object> {
        props: T;
    }
    // 旧的代码不用改,新的代码可以用泛型
    let oldComponent = new Component();  // T = Object,兼容旧代码
    let newComponent = new Component<string>();  // 新代码用泛型
    
  3. 降低API使用门槛(大部分场景只需指定关键类型)

    // 复杂泛型,但大部分情况只需要指定一个
    interface Query<T, ErrorType = Error> {
        data: T;
        error: ErrorType;
    }
    // 通常只指定 T 就够了
    let q1: Query<User>;
    

重要澄清

  • 默认类型不会在类型推断失败时自动启用
  • 默认类型是为了减少需要显式指定的类型参数数量

对比C++

// C++ 模板默认参数
template<typename T = std::string>
class Logger {
    T prefix;
public:
    Logger(T p) : prefix(p) {}
    void log(const std::string& msg) {
        std::cout << prefix << ": " << msg << std::endl;
    }
};

Logger<> defaultLogger("INFO");
Logger<int> numberLogger(1001);

5.2 泛型工厂模式

// 工厂接口
interface Factory<T> {
create(): T;
}

// 具体产品
class Device {
  id: number;
  name: string;

  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }
}

class Sensor {
  type: string;
  value: number;

  constructor(type: string) {
    this.type = type;
    this.value = 0;
  }
}

// 泛型工厂实现
class DeviceFactory implements Factory<Device> {
  private nextId: number = 1;

  create(): Device {
    return new Device(this.nextId++, `设备-${this.nextId}`);
  }
}

class SensorFactory implements Factory<Sensor> {
  create(): Sensor {
    return new Sensor("温度");
  }
}

// 泛型工厂使用函数
function createProducts<T>(factory: Factory<T>, count: number): T[] {
  let products: T[] = [];
  for (let i: number = 0; i < count; i++) {
    products.push(factory.create());
  }
  return products;
}

// 使用
let deviceFactory = new DeviceFactory();
let devices = createProducts(deviceFactory, 3);
console.log(`创建了 ${devices.length} 个设备`);

5.3 泛型仓库模式

// 实体接口
interface Entity {
id: number;
}

// 带约束的泛型仓库
class GenericRepository<T extends Entity> {
  private items: Map<number, T> = new Map();

  add(item: T): void {
    this.items.set(item.id, item);
  }

  get(id: number): T {
    let item = this.items.get(id);
    if (item === undefined) {
      throw new Error(`未找到: ${id}`);
    }
    return item;
  }

  getAll(): T[] {
    let result: T[] = [];
    this.items.forEach((value: T) => {
      result.push(value);
    });
    return result;
  }

  update(item: T): void {
    if (!this.items.has(item.id)) {
      throw new Error(`不存在: ${item.id}`);
    }
    this.items.set(item.id, item);
  }

  delete(id: number): boolean {
    return this.items.delete(id);
  }

  find(predicate: (item: T) => boolean): T[] {
    let result: T[] = [];
    this.items.forEach((value: T) => {
      if (predicate(value)) {
        result.push(value);
      }
    });
    return result;
  }
}

// 具体实体
class Product implements Entity {
  id: number;
  name: string;
  price: number;

  constructor(id: number, name: string, price: number) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

// 使用
let productRepo = new GenericRepository<Product>();
productRepo.add(new Product(1, "手机", 2999));
productRepo.add(new Product(2, "平板", 3999));
productRepo.add(new Product(3, "耳机", 999));

// 查找价格大于1000的产品
let expensive = productRepo.find((p: Product) => p.price > 1000);
console.log(`高价商品: ${expensive.length} 个`);

Repository(仓库)命名说明
Repository 是领域驱动设计(DDD)中的模式,用于封装对数据的访问逻辑。与 DAO(Data Access Object)相比,Repository 更面向领域,操作的是实体对象而非数据库表。


第六部分:阶段三总结

6.1 OOP知识全景图

Day 09: 类基础
├── 类定义 (class)
├── 属性与构造函数
├── 访问修饰符 (public/private/protected)
├── readonly 属性
├── Getter/Setter
├── 静态成员 (static)

Day 10: 继承与多态
├── extends 继承
├── super 关键字
├── 方法重写 (Override)
├── 多态 (Polymorphism)
├── instanceof 类型判断
└── 抽象类 (abstract)

Day 11: 接口与类型
├── interface 定义
├── implements 实现
├── 接口继承 (extends)
├── 多重接口继承

Day 12: 泛型
├── 泛型函数
├── 泛型约束 (extends)
├── 泛型类
├── 泛型接口
└── 泛型实用模式

6.2 关键概念对比表

Class vs Interface vs Abstract Class

特性 Class Interface Abstract Class
实例化 ✅ 可以 ❌ 不可以 ❌ 不可以
实现代码 ✅ 可以 ❌ 不可以 ✅ 可以(部分)
抽象成员 ❌ 不可以 ✅ 必须抽象 ✅ 可以有
继承 extends(单继承) extends(多继承) extends(单继承)
实现 被 implements implements 被 extends
运行时 存在 擦除 存在
用途 创建对象 定义契约 基类模板

ArkTS vs C++ OOP 完整对比

概念 C++ ArkTS
类定义 class Name {} class Name {}
构造函数 Name(params) constructor(params)
析构函数 ~Name() 自动垃圾回收
继承 : public Base extends Base
多继承 ✅ 支持 ❌ 不支持(类)
接口多继承 ❌ 不支持 ✅ 支持
访问修饰符 public:/private:/protected: 每个成员单独标记
虚函数 virtual 自动虚函数
纯虚函数 virtual void f() = 0 abstract method()
抽象类 含纯虚函数的类 abstract class
静态成员 static static
const成员 const readonly
函数模板 template<typename T> <T> 泛型
类模板 template<class T> class Name<T>
模板约束 C++20 concepts extends Interface
类型推断 auto / CTAD 自动推断

6.3 从C/C++到ArkTS的OOP差异总结

1. 内存管理

// C++:手动管理内存
Device* d = new Device();
// ... 使用 d ...
delete d;  // 必须手动释放
// ArkTS:自动垃圾回收
let d = new Device();
// ... 使用 d ...
// 无需手动释放,垃圾回收器自动处理

2. 继承模型

// C++:多继承
class D : public A, public B, public C {};  // ✅ 允许
// ArkTS:类单继承,接口多继承
class D extends A implements B, C {
} // ✅ 类单继承+接口多实现
interface D extends A, B, C {} // ✅ 接口多继承

3. 虚函数机制

// C++:需要显式声明 virtual
class Base {
public:
    virtual void method();  // 虚函数
    void nonVirtual();       // 非虚函数
};
// ArkTS:所有方法都是"虚函数"
class Base {
  method(): void {
  } // 自动可被重写

  readonly finalMethod = (): void => {
  } // 类似 final
}

4. 泛型/模板

// C++:模板元编程强大但复杂
template<typename T>
class Container {
    T* data;
public:
    Container() : data(new T[100]) {}
    ~Container() { delete[] data; }
};
// ArkTS:泛型更简单,类型擦除
class Container<T> {
  private data: T[] = []; // 直接使用数组
  // 无需手动管理内存
}

5. 类型系统

C++ ArkTS
编译期强类型 编译期强类型
模板实例化生成代码 泛型类型擦除
头文件/源文件分离 单文件模块
指针/引用 引用类型(无指针运算)

第七部分:综合练习

综合练习题

题1:泛型函数

实现一个泛型函数 reverseArray<T>,接收一个数组并返回反转后的新数组。

// 要求:
// 1. 使用泛型实现
// 2. 不修改原数组
// 3. 保持类型安全

// 测试用例:
let nums = [1, 2, 3, 4, 5];
let reversedNums = reverseArray(nums);
console.log(reversedNums); // [5, 4, 3, 2, 1]

let strs = ["a", "b", "c"];
let reversedStrs = reverseArray(strs);
console.log(reversedStrs); // ["c", "b", "a"]
参考答案
function reverseArray<T>(arr: T[]): T[] {
  let result: T[] = [];
  for (let i: number = arr.length - 1; i >= 0; i--) {
    result.push(arr[i]);
  }
  return result;
}

题2:泛型约束

定义一个 Printable 接口(包含 toString(): string 方法),实现一个泛型函数 printItems<T>,要求传入的数组元素必须实现 Printable

// 要求:
// 1. 定义 Printable 接口
// 2. 使用泛型约束
// 3. 打印每个元素的 toString() 结果

// 测试用例:
class Person implements Printable {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  toString(): string {
    return `Person(${this.name})`;
  }
}

let people = [new Person("Alice"), new Person("Bob")];
printItems(people);
// 输出:
// Person(Alice)
// Person(Bob)
参考答案
interface Printable {
toString(): string;
}

function printItems<T extends Printable>(items: T[]): void {
  for (let i: number = 0; i < items.length; i++) {
    console.log(items[i].toString());
  }
}

题3:泛型类

实现一个泛型队列 Queue<T>,支持以下操作:

  • enqueue(item: T): void - 入队
  • dequeue(): T - 出队
  • peek(): T - 查看队首
  • isEmpty(): boolean - 是否为空
  • size(): number - 获取大小
// 测试用例:
let queue = new Queue<number>();
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
console.log(queue.dequeue()); // 10
console.log(queue.peek()); // 20
console.log(queue.size()); // 2
参考答案
class Queue<T> {
  private items: T[] = [];

  enqueue(item: T): void {
    this.items.push(item);
  }

  dequeue(): T {
    if (this.isEmpty()) {
      throw new Error("队列为空");
    }
    return this.items.shift() as T;
  }

  peek(): T {
    if (this.isEmpty()) {
      throw new Error("队列为空");
    }
    return this.items[0];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }

  size(): number {
    return this.items.length;
  }
}

题4:泛型接口与实现

定义泛型接口 Cache<K, V>(键值对缓存),实现类 MemoryCache<K, V>

// 接口要求:
// - set(key: K, value: V): void
// - get(key: K): V
// - has(key: K): boolean
// - delete(key: K): boolean
// - clear(): void

// 测试用例:
let cache = new MemoryCache<string, number>();
cache.set("a", 100);
cache.set("b", 200);
console.log(cache.get("a")); // 100
console.log(cache.has("b")); // true
cache.delete("a");
console.log(cache.has("a")); // false
参考答案
interface Cache<K, V> {
set(key: K, value: V): void;
get(key: K): V;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
}

class MemoryCache<K, V> implements Cache<K, V> {
  private data: Map<K, V> = new Map();

  set(key: K, value: V): void {
    this.data.set(key, value);
  }

  get(key: K): V {
    let value = this.data.get(key);
    if (value === undefined) {
      throw new Error(`Key not found: ${key}`);
    }
    return value;
  }

  has(key: K): boolean {
    return this.data.has(key);
  }

  delete(key: K): boolean {
    return this.data.delete(key);
  }

  clear(): void {
    this.data.clear();
  }
}

题5:多重约束

定义两个接口 Named(有 name 属性)和 Aged(有 age 属性),创建一个合并接口 Person,实现泛型函数 greet<T>

// 要求:
// 1. Named 接口:name: string
// 2. Aged 接口:age: number
// 3. Person 接口继承两者
// 4. greet<T extends Person> 输出问候语包含 name 和 age

// 测试用例:
class Student implements Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

greet(new Student("小明", 20));
// 输出:你好,小明!今年20岁。
参考答案
interface Named {
name: string;
}

interface Aged {
age: number;
}

interface Person extends Named, Aged {}

function greet<T extends Person>(person: T): void {
  console.log(`你好,${person.name}!今年${person.age}岁。`);
}

题6:类 + 接口 + 泛型综合

创建一个设备管理系统,要求:

  1. 定义接口 Device(id, name, turnOn(), turnOff())
  2. 定义抽象类 BaseDevice 实现 Device
  3. 创建具体类 LightFan 继承 BaseDevice
  4. 创建泛型类 DeviceManager<T extends Device> 管理设备
参考答案
interface Device {
id: number;
name: string;
turnOn(): void;
turnOff(): void;
}

abstract class BaseDevice implements Device {
  id: number;
  name: string;
  protected isOn: boolean = false;

  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }

  abstract turnOn(): void;

  abstract turnOff(): void;

  getStatus(): string {
    return this.isOn ? "开启" : "关闭";
  }
}

class Light extends BaseDevice {
  brightness: number = 100;

  turnOn(): void {
    this.isOn = true;
    console.log(`${this.name} 灯已开启,亮度${this.brightness}%`);
  }

  turnOff(): void {
    this.isOn = false;
    console.log(`${this.name} 灯已关闭`);
  }
}

class Fan extends BaseDevice {
  speed: number = 1;

  turnOn(): void {
    this.isOn = true;
    console.log(`${this.name} 风扇已开启,档位${this.speed}`);
  }

  turnOff(): void {
    this.isOn = false;
    console.log(`${this.name} 风扇已关闭`);
  }
}

class DeviceManager<T extends Device> {
  private devices: T[] = [];

  add(device: T): void {
    this.devices.push(device);
  }

  turnOnAll(): void {
    for (let i: number = 0; i < this.devices.length; i++) {
      this.devices[i].turnOn();
    }
  }

  turnOffAll(): void {
    for (let i: number = 0; i < this.devices.length; i++) {
      this.devices[i].turnOff();
    }
  }

  findById(id: number): T {
    for (let i: number = 0; i < this.devices.length; i++) {
      if (this.devices[i].id === id) {
        return this.devices[i];
      }
    }
    throw new Error(`未找到设备: ${id}`);
  }
}

// 使用
let manager = new DeviceManager<BaseDevice>();
manager.add(new Light(1, "客厅灯"));
manager.add(new Fan(2, "吊扇"));
manager.turnOnAll();

题7:泛型工具函数

实现以下泛型工具函数:

  1. pick<T, K> - 从对象 T 中选取指定属性 K 组成新对象
  2. omit<T, K> - 从对象 T 中排除指定属性 K 组成新对象

注意:由于 ArkTS 限制,使用简化版本,只处理单层属性。

参考答案
// 简化版本,使用 Record 和类型断言
function pick<T extends Record<string, Object>, K extends keyof T>(
  obj: T,
  keys: K[]
): Pick<T, K> {
  let result: Record<string, Object> = {};
  for (let i: number = 0; i < keys.length; i++) {
    let key = keys[i];
    result[key as string] = obj[key];
  }
  return result as Pick<T, K>;
}

// 使用示例
interface User {
id: number;
name: string;
email: string;
age: number;
}

let user: User = {
  id: 1,
  name: "张三",
  email: "zs@example.com",
  age: 25
};
let picked = pick(user, ["id", "name"]);
console.log(picked.id); // 1
console.log(picked.name); // "张三"

题8:泛型默认值

创建一个泛型类 Response<T = string>,表示 API 响应:

  • data: T - 响应数据
  • code: number - 状态码
  • message: string - 消息

实现默认类型为 string 的版本和指定类型的版本。

参考答案
class Response<T = string> {
  data: T;
  code: number;
  message: string;

  constructor(data: T, code: number = 200, message: string = "OK") {
    this.data = data;
    this.code = code;
    this.message = message;
  }

  isSuccess(): boolean {
    return this.code >= 200 && this.code < 300;
  }
}

// 使用默认类型(string)
let strResponse = new Response("操作成功");
console.log(strResponse.data); // "操作成功"

// 指定类型
interface UserData {
id: number;
name: string;
}

let userResponse = new Response<UserData>({ id: 1, name: "张三" });
console.log(userResponse.data.name); // "张三"

题9:泛型与枚举

结合泛型和枚举,实现一个状态机管理器:

  1. 定义枚举 Status(Pending, Active, Completed, Failed)
  2. 定义接口 Task(id, status, execute())
  3. 创建泛型类 StateMachine<T extends Task> 管理任务状态转换
参考答案
enum Status {
Pending = "PENDING",
Active = "ACTIVE",
Completed = "COMPLETED",
Failed = "FAILED"
}

interface Task {
id: number;
name: string;
status: Status;
execute(): void;
}

class StateMachine<T extends Task> {
  private task: T;
  private history: Status[] = [];

  constructor(task: T) {
    this.task = task;
    this.history.push(task.status);
  }

  transitionTo(newStatus: Status): boolean {
    // 简单状态转换规则
    let validTransitions: Record<string, Status[]> = {
      [Status.Pending]: [Status.Active],
      [Status.Active]: [Status.Completed, Status.Failed],
      [Status.Completed]: [],
      [Status.Failed]: [Status.Pending]
    };

    let allowed = validTransitions[this.task.status];
    for (let i: number = 0; i < allowed.length; i++) {
      if (allowed[i] === newStatus) {
        this.task.status = newStatus;
        this.history.push(newStatus);
        return true;
      }
    }
    return false;
  }

  getHistory(): Status[] {
    return this.history;
  }

  getCurrentStatus(): Status {
    return this.task.status;
  }
}

// 具体任务类
class DownloadTask implements Task {
  id: number;
  name: string;
  status: Status = Status.Pending;
  url: string;

  constructor(id: number, name: string, url: string) {
    this.id = id;
    this.name = name;
    this.url = url;
  }

  execute(): void {
    console.log(`开始下载: ${this.url}`);
  }
}

// 使用
let download = new DownloadTask(1, "文件下载", "http://example.com/file.zip");
let sm = new StateMachine(download);
console.log(sm.transitionTo(Status.Active)); // true
console.log(sm.transitionTo(Status.Completed)); // true
console.log(sm.transitionTo(Status.Pending)); // false(Completed不能转Pending)

题10:综合设计

设计一个可扩展的插件系统:

  1. 定义接口 Plugin<T>(name, version, init(config: T), execute())
  2. 创建泛型类 PluginManager<T> 管理插件
  3. 实现两个具体插件:日志插件(配置为日志级别)和数据插件(配置为数据源)
参考答案
// 插件接口
interface Plugin<T> {
name: string;
version: string;
config: T;
init(config: T): void;
execute(): void;
}

// 插件管理器
class PluginManager<T> {
  private plugins: Plugin<T>[] = [];

  register(plugin: Plugin<T>): void {
    this.plugins.push(plugin);
    console.log(`注册插件: ${plugin.name} v${plugin.version}`);
  }

  initAll(config: T): void {
    for (let i: number = 0; i < this.plugins.length; i++) {
      this.plugins[i].init(config);
    }
  }

  executeAll(): void {
    for (let i: number = 0; i < this.plugins.length; i++) {
      this.plugins[i].execute();
    }
  }
}

// 日志插件配置
interface LogConfig {
level: string;
output: string;
}

class LogPlugin implements Plugin<LogConfig> {
  name: string = "LogPlugin";
  version: string = "1.0.0";
  config: LogConfig = { level: "INFO", output: "console" };

  init(config: LogConfig): void {
    this.config = config;
    console.log(`日志插件初始化,级别: ${config.level}`);
  }

  execute(): void {
    console.log(`[${this.config.level}] 执行日志记录`);
  }
}

// 数据插件配置
interface DataConfig {
source: string;
timeout: number;
}

class DataPlugin implements Plugin<DataConfig> {
  name: string = "DataPlugin";
  version: string = "1.0.0";
  config: DataConfig = { source: "default", timeout: 5000 };

  init(config: DataConfig): void {
    this.config = config;
    console.log(`数据插件初始化,源: ${config.source}`);
  }

  execute(): void {
    console.log(`从 ${this.config.source} 获取数据`);
  }
}

// 使用
let logManager = new PluginManager<LogConfig>();
logManager.register(new LogPlugin());
logManager.initAll({ level: "DEBUG", output: "file" });
logManager.executeAll();

let dataManager = new PluginManager<DataConfig>();
dataManager.register(new DataPlugin());
dataManager.initAll({ source: "database", timeout: 10000 });
dataManager.executeAll();
posted @ 2026-04-15 18:47  thammer  阅读(8)  评论(0)    收藏  举报