适配器模式(Adapter Pattern)是一种结构型设计模式,通过创建一个适配器类,将不兼容的接口转换为客户端期望的统一接口。其核心价值在于:
- 接口统一:将不同数据源或系统的接口统一为一致的访问方式,简化客户端代码。
- 解耦设计:通过适配器隔离客户端与具体实现,降低耦合度。
- 复用增强:使原本不兼容的类或系统能够协同工作,提升代码复用率。
- 扩展灵活:遵循开闭原则,新增数据源只需实现新适配器,无需修改现有代码。
模式结构解析
适配器模式通常包含以下角色:
- Target:客户端期望的统一接口(如查询接口)。
- Adapter:实现目标接口,内部调用被适配对象的接口。
- Adaptee:需要适配的现有接口(如数据库查询、XML解析)。
- Client:使用目标接口的客户端代码。
多数据源统一查询
在企业级应用中,数据可能来自内存数组、关系型数据库(如MySQL、DMDB)或XML文件等多种来源。每种数据源的访问方式差异较大,例如内存数组使用JavaScript数组方法,数据库依赖SQL查询,XML文件需要解析操作。通过适配器模式,我们可以为所有数据源提供统一的查询接口,客户端无需关心底层实现。
统一查询接口设计
我们设计一个IQueryable<T>
接口,模仿LINQ风格,支持链式查询操作(如where
、select
、orderBy
等),并定义数据源类型枚举和分组接口。
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的
LIMIT
和OFFSET
)。 - 实现延迟执行(Lazy Evaluation)以减少不必要的计算。
- 对于大数据源,使用分页加载(如SQL的
-
错误处理:统一捕获和封装不同数据源的异常,提供一致的错误信息。
-
类型映射:确保不同数据源的数据类型正确映射,防止运行时错误。
-
缓存策略:对频繁查询的数据源实现结果缓存,提升性能。
-
异步优化 :对于异步数据源,尽量减少阻塞操作,使用
Promise
或async/await
。
总结
通过适配器模式,我们构建了一个可扩展、类型安全的统一数据访问层,解决了多数据源接口不兼容的问题。客户端只需使用统一的IQueryable
接口,即可操作内存、数据库、XML等多种数据源,极大提高了代码复用性和维护性。未来新增数据源(如JSON文件、远程API)只需实现新的适配器,系统即可无缝扩展。