TypeScript设计模式:适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,通过创建一个适配器类,将不兼容的接口转换为客户端期望的统一接口。其核心价值在于:

  • 接口统一:将不同数据源或系统的接口统一为一致的访问方式,简化客户端代码。
  • 解耦设计:通过适配器隔离客户端与具体实现,降低耦合度。
  • 复用增强:使原本不兼容的类或系统能够协同工作,提升代码复用率。
  • 扩展灵活:遵循开闭原则,新增数据源只需实现新适配器,无需修改现有代码。

模式结构解析

适配器模式通常包含以下角色:

  • Target:客户端期望的统一接口(如查询接口)。
  • Adapter:实现目标接口,内部调用被适配对象的接口。
  • Adaptee:需要适配的现有接口(如数据库查询、XML解析)。
  • Client:使用目标接口的客户端代码。

多数据源统一查询

在企业级应用中,数据可能来自内存数组、关系型数据库(如MySQL、DMDB)或XML文件等多种来源。每种数据源的访问方式差异较大,例如内存数组使用JavaScript数组方法,数据库依赖SQL查询,XML文件需要解析操作。通过适配器模式,我们可以为所有数据源提供统一的查询接口,客户端无需关心底层实现。

统一查询接口设计

我们设计一个IQueryable<T>接口,模仿LINQ风格,支持链式查询操作(如whereselectorderBy等),并定义数据源类型枚举和分组接口。

typescript 复制代码
// 定义统一查询接口,模仿LINQ风格,支持链式操作
interface IQueryable<T> {
  // 过滤数据
  where(predicate: (item: T) => boolean): IQueryable<T>;
  // 投影数据到新类型
  select<U>(selector: (item: T) => U): IQueryable<U>;
  // 按键排序
  orderBy<K>(keySelector: (item: T) => K): IQueryable<T>;
  // 按键分组
  groupBy<K>(keySelector: (item: T) => K): IQueryable<IGrouping<K, T>>;
  // 取前N条数据
  take(count: number): IQueryable<T>;
  // 将结果转换为数组
  toArray(): T[];
}

// 分组接口,表示按键分组后的数据结构
interface IGrouping<K, T> {
  key: K; // 分组键
  items: T[]; // 分组项
}

// 数据源类型枚举
enum DataSourceType {
  Memory, // 内存数组
  Database, // 数据库
  XmlFile, // XML文件
}

多数据源适配器实现

内存数组适配器

MemoryArrayAdapter适配内存数组,支持链式查询操作,通过JavaScript数组方法实现功能。

typescript 复制代码
// 内存数组适配器,适配JavaScript数组的查询操作
class MemoryArrayAdapter<T> implements IQueryable<T> {
  constructor(private data: T[]) {}

  // 过滤数据,返回新的适配器实例以支持链式调用
  where(predicate: (item: T) => boolean): IQueryable<T> {
    const filtered = this.data.filter(predicate);
    return new MemoryArrayAdapter(filtered);
  }

  // 投影数据到新类型
  select<U>(selector: (item: T) => U): IQueryable<U> {
    const mapped = this.data.map(selector);
    return new MemoryArrayAdapter(mapped);
  }

  // 按指定键排序
  orderBy<K>(keySelector: (item: T) => K): IQueryable<T> {
    const sorted = [...this.data].sort((a, b) => {
      const keyA = keySelector(a);
      const keyB = keySelector(b);
      return keyA > keyB ? 1 : keyA < keyB ? -1 : 0;
    });
    return new MemoryArrayAdapter(sorted);
  }

  // 按键分组
  groupBy<K>(keySelector: (item: T) => K): IQueryable<IGrouping<K, T>> {
    const groups = new Map<K, T[]>();
    this.data.forEach(item => {
      const key = keySelector(item);
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(item);
    });
    const result = Array.from(groups.entries()).map(([key, items]) => ({ key, items }));
    return new MemoryArrayAdapter(result);
  }

  // 取前N条数据
  take(count: number): IQueryable<T> {
    const taken = this.data.slice(0, count);
    return new MemoryArrayAdapter(taken);
  }

  // 返回数据数组副本
  toArray(): T[] {
    return [...this.data];
  }
}

数据库适配器(MySQL/DMDB)

DatabaseAdapter适配数据库查询,将SQL结果转换为统一接口。由于数据库操作通常是异步的,方法返回Promise

typescript 复制代码
// 数据库适配器,适配SQL数据库查询
class DatabaseAdapter<T> implements IQueryable<T> {
  constructor(
    private connection: any, // 数据库连接对象
    private tableName: string // 表名
  ) {}

  // 异步过滤数据
  async where(predicate: (item: T) => boolean): Promise<IQueryable<T>> {
    const result = await this.executeQuery(`SELECT * FROM ${this.tableName}`);
    return new MemoryArrayAdapter(result.filter(predicate));
  }

  // 异步投影数据
  async select<U>(selector: (item: T) => U): Promise<IQueryable<U>> {
    const result = await this.executeQuery(`SELECT * FROM ${this.tableName}`);
    const mapped = result.map(selector);
    return new MemoryArrayAdapter(mapped);
  }

  // 异步按键排序
  async orderBy<K>(keySelector: (item: T) => K): Promise<IQueryable<T>> {
    const result = await this.executeQuery(`SELECT * FROM ${this.tableName}`);
    const sorted = [...result].sort((a, b) => {
      const keyA = keySelector(a);
      const keyB = keySelector(b);
      return keyA > keyB ? 1 : keyA < keyB ? -1 : 0;
    });
    return new MemoryArrayAdapter(sorted);
  }

  // 异步按键分组
  async groupBy<K>(keySelector: (item: T) => K): Promise<IQueryable<IGrouping<K, T>>> {
    const result = await this.executeQuery(`SELECT * FROM ${this.tableName}`);
    const groups = new Map<K, T[]>();
    result.forEach((item: T) => {
      const key = keySelector(item);
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(item);
    });
    const grouped = Array.from(groups.entries()).map(([key, items]) => ({ key, items }));
    return new MemoryArrayAdapter(grouped);
  }

  // 异步取前N条数据
  async take(count: number): Promise<IQueryable<T>> {
    const result = await this.executeQuery(`SELECT * FROM ${this.tableName} LIMIT ${count}`);
    return new MemoryArrayAdapter(result);
  }

  // 异步返回数据数组
  async toArray(): Promise<T[]> {
    return await this.executeQuery(`SELECT * FROM ${this.tableName}`);
  }

  // 执行SQL查询(模拟实现,实际需替换为真实数据库操作)
  private async executeQuery(query: string): Promise<T[]> {
    // TODO: 替换为实际数据库查询逻辑
    return Promise.resolve([]);
  }
}

XML文件适配器

XmlFileAdapter适配XML文件数据源,通过xml2js解析XML并转换为统一接口。

typescript 复制代码
import * as xml2js from 'xml2js';
import * as fs from 'fs';

// XML文件适配器,适配XML文件查询
class XmlFileAdapter<T> implements IQueryable<T> {
  constructor(private filePath: string) {}

  // 异步过滤数据
  async where(predicate: (item: T) => boolean): Promise<IQueryable<T>> {
    const data = await this.loadData();
    const filtered = data.filter(predicate);
    return new MemoryArrayAdapter(filtered);
  }

  // 异步投影数据
  async select<U>(selector: (item: T) => U): Promise<IQueryable<U>> {
    const data = await this.loadData();
    const mapped = data.map(selector);
    return new MemoryArrayAdapter(mapped);
  }

  // 异步按键排序
  async orderBy<K>(keySelector: (item: T) => K): Promise<IQueryable<T>> {
    const data = await this.loadData();
    const sorted = [...data].sort((a, b) => {
      const keyA = keySelector(a);
      const keyB = keySelector(b);
      return keyA > keyB ? 1 : keyA < keyB ? -1 : 0;
    });
    return new MemoryArrayAdapter(sorted);
  }

  // 异步按键分组
  async groupBy<K>(keySelector: (item: T) => K): Promise<IQueryable<IGrouping<K, T>>> {
    const data = await this.loadData();
    const groups = new Map<K, T[]>();
    data.forEach(item => {
      const key = keySelector(item);
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(item);
    });
    const grouped = Array.from(groups.entries()).map(([key, items]) => ({ key, items }));
    return new MemoryArrayAdapter(grouped);
  }

  // 异步取前N条数据
  async take(count: number): Promise<IQueryable<T>> {
    const data = await this.loadData();
    const taken = data.slice(0, count);
    return new MemoryArrayAdapter(taken);
  }

  // 异步返回数据数组
  async toArray(): Promise<T[]> {
    return await this.loadData();
  }

  // 加载并解析XML文件
  async loadData(): Promise<T[]> {
    const xml = fs.readFileSync(this.filePath, 'utf8');
    const result = await xml2js.parseStringPromise(xml);
    return this.convertXmlToObjects(result);
  }

  // 将XML解析结果转换为对象数组(模拟实现)
  private convertXmlToObjects(xml: any): T[] {
    // TODO: 替换为实际XML转换逻辑
    return [];
  }
}

统一查询服务

QueryService作为工厂类,根据数据源类型创建对应的适配器实例。

typescript 复制代码
// 查询服务工厂,创建不同数据源的适配器
class QueryService {
  static createQuery<T>(sourceType: DataSourceType, config: any): IQueryable<T> {
    switch (sourceType) {
      case DataSourceType.Memory:
        return new MemoryArrayAdapter(config.data);
      case DataSourceType.Database:
        return new DatabaseAdapter(config.connection, config.tableName);
      case DataSourceType.XmlFile:
        return new XmlFileAdapter(config.filePath);
      default:
        throw new Error('Unsupported data source type');
    }
  }
}

LINQ类实现

Linq类提供静态方法,简化适配器创建过程,支持从不同数据源构造查询。

typescript 复制代码
// LINQ风格查询入口
class Linq {
  // 从内存数组创建查询
  static from<T>(data: T[]): IQueryable<T> {
    return QueryService.createQuery(DataSourceType.Memory, { data });
  }

  // 从数据库创建查询
  static fromDatabase<T>(tableName: string, connection: any = {}): IQueryable<T> {
    return QueryService.createQuery(DataSourceType.Database, { connection, tableName });
  }

  // 从XML文件创建查询
  static fromXmlFile<T>(filePath: string): IQueryable<T> {
    return QueryService.createQuery(DataSourceType.XmlFile, { filePath });
  }
}

LINQ风格查询

统一查询示例

以下示例展示如何使用统一接口查询不同数据源,代码简洁且一致。

typescript 复制代码
// 示例用户数据接口
interface User {
  name: string;
  age: number;
  salary: number;
  city: string;
}

// 示例内存数据
const users: User[] = [
  { name: 'Alice', age: 30, salary: 50000, city: 'London' },
  { name: 'Bob', age: 25, salary: 60000, city: 'Paris' },
  { name: 'Charlie', age: 35, salary: 70000, city: 'London' },
];

// 内存数组查询:筛选年龄大于28的用户,投影姓名和薪资
const memoryResult = Linq.from(users)
  .where(u => u.age > 28)
  .select(u => ({ name: u.name, salary: u.salary }))
  .toArray();

// 数据库查询:筛选伦敦用户并按薪资排序
const dbResult = await Linq.fromDatabase<User>('users')
  .where(u => u.city === 'London')
  .orderBy(u => u.salary)
  .toArray();

// XML文件查询:按城市分组
const xmlResult = await Linq.fromXmlFile<User>('users.xml')
  .groupBy(u => u.city)
  .toArray();

异步适配器扩展

为支持异步数据源(如远程API),我们实现AsyncQueryAdapter,将异步操作封装为统一接口。

typescript 复制代码
// 异步查询适配器,适配异步数据源
class AsyncQueryAdapter<T> {
  constructor(private dataPromise: Promise<T[]>) {}

  // 异步过滤数据
  async where(predicate: (item: T) => boolean): Promise<AsyncQueryAdapter<T>> {
    const data = await this.dataPromise;
    return new AsyncQueryAdapter(Promise.resolve(data.filter(predicate)));
  }

  // 异步投影数据
  async select<U>(selector: (item: T) => U): Promise<AsyncQueryAdapter<U>> {
    const data = await this.dataPromise;
    return new AsyncQueryAdapter(Promise.resolve(data.map(selector)));
  }

  // 异步按键排序
  async orderBy<K>(keySelector: (item: T) => K): Promise<AsyncQueryAdapter<T>> {
    const data = await this.dataPromise;
    const sorted = [...data].sort((a, b) => {
      const keyA = keySelector(a);
      const keyB = keySelector(b);
      return keyA > keyB ? 1 : keyA < keyB ? -1 : 0;
    });
    return new AsyncQueryAdapter(Promise.resolve(sorted));
  }

  // 异步按键分组
  async groupBy<K>(keySelector: (item: T) => K): Promise<AsyncQueryAdapter<IGrouping<K, T>>> {
    const data = await this.dataPromise;
    const groups = new Map<K, T[]>();
    data.forEach(item => {
      const key = keySelector(item);
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(item);
    });
    const grouped = Array.from(groups.entries()).map(([key, items]) => ({ key, items }));
    return new AsyncQueryAdapter(Promise.resolve(grouped));
  }

  // 异步取前N条数据
  async take(count: number): Promise<AsyncQueryAdapter<T>> {
    const data = await this.dataPromise;
    const taken = data.slice(0, count);
    return new AsyncQueryAdapter(Promise.resolve(taken));
  }

  // 异步返回数据数组
  async toArray(): Promise<T[]> {
    return await this.dataPromise;
  }
}

模式优势与适用场景

核心优势

  • 接口统一:客户端使用一致的查询语法,无需关心数据源差异。
  • 解耦设计:适配器隔离客户端与具体数据源实现,降低耦合。
  • 扩展性强:新增数据源只需实现新适配器,符合开闭原则。
  • 类型安全:TypeScript泛型确保查询操作的类型一致性。

典型应用场景

  • 数据库访问层:统一封装MySQL、PostgreSQL等数据库的查询逻辑。
  • 多格式数据处理:处理JSON、XML、CSV等多种数据格式。
  • 第三方API适配:将不同API的响应格式转换为统一模型。
  • 遗留系统改造:为老旧系统接口提供现代化包装。
  • 微服务数据接口:统一微服务间的数据访问接口。

最佳实践与注意事项

  • 单一职责原则:适配器仅负责接口转换,避免掺杂业务逻辑。

  • 性能优化

    • 对于大数据源,使用分页加载(如SQL的LIMITOFFSET)。
    • 实现延迟执行(Lazy Evaluation)以减少不必要的计算。
  • 错误处理:统一捕获和封装不同数据源的异常,提供一致的错误信息。

  • 类型映射:确保不同数据源的数据类型正确映射,防止运行时错误。

  • 缓存策略:对频繁查询的数据源实现结果缓存,提升性能。

  • 异步优化 :对于异步数据源,尽量减少阻塞操作,使用Promiseasync/await

总结

通过适配器模式,我们构建了一个可扩展、类型安全的统一数据访问层,解决了多数据源接口不兼容的问题。客户端只需使用统一的IQueryable接口,即可操作内存、数据库、XML等多种数据源,极大提高了代码复用性和维护性。未来新增数据源(如JSON文件、远程API)只需实现新的适配器,系统即可无缝扩展。

相关推荐
该用户已不存在2 小时前
Mojo vs Python vs Rust: 2025年搞AI,该学哪个?
后端·python·rust
遂心_2 小时前
深入理解 React Hook:useEffect 完全指南
前端·javascript·react.js
Moonbit2 小时前
MoonBit 正式加入 WebAssembly Component Model 官方文档 !
前端·后端·编程语言
龙在天2 小时前
ts中的函数重载
前端
Goland猫2 小时前
电商架构图
后端
卓伊凡3 小时前
非常经典的Android开发问题-mipmap图标目录和drawable图标目录的区别和适用场景实战举例-优雅草卓伊凡
前端
前端Hardy3 小时前
HTML&CSS: 谁懂啊!用代码 “擦去”图片雾气
前端·javascript·css
前端Hardy3 小时前
HTML&CSS:好精致的导航栏
前端·javascript·css
Java中文社群3 小时前
重要:Java25正式发布(长期支持版)!
java·后端·面试