06. 适配器模式(Adapter Pattern)
分类: 结构型模式
热门度: ★★★★☆
难度: ★★☆☆☆
📖 概念
适配器模式将一个类的接口转换成客户端期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。它就像电源适配器一样,把不匹配的插头转换成匹配的接口。
适配器有两种实现方式:类适配器(通过多重继承)和对象适配器(通过组合,更常用)。
🎯 意图
将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,实现接口之间的兼容。
🔑 关键角色
| 目标接口(Target) | 客户端期望的接口 |
| 被适配者(Adaptee) | 已存在的、接口不兼容的类 |
| 适配器(Adapter) | 将被适配者的接口转换为目标接口 |
| 客户端(Client) | 通过目标接口使用适配器 |
⚠️ 注意事项
- 对象适配器(组合)比类适配器(继承)更灵活,推荐使用
- 适配器不应增加过多的转换逻辑,否则说明两个接口差异太大
- 适配器模式是事后补救措施,如果能在设计阶段统一接口更好
- 可以结合工厂模式来动态选择适配器
- 注意被适配者的异常处理,适配器应该转换或包装异常
🔄 实现流程
客户端 → 通过目标接口调用方法
→ 适配器接收调用
→ 适配器将调用转发给被适配者的方法
→ 被适配者执行实际操作
→ 适配器将结果转换为目标接口格式
→ 返回结果给客户端
💡 常见使用场景
场景1: 第三方API集成(Third-party API Integration)
将第三方支付SDK的接口适配为系统统一的支付接口。
using System;
// 目标接口 – 系统统一支付接口
public interface IPaymentGateway
{
PaymentResult Charge(string orderId, decimal amount, string currency);
PaymentResult Refund(string transactionId, decimal amount);
}
public class PaymentResult
{
public bool Success { get; set; }
public string TransactionId { get; set; }
public string Message { get; set; }
}
// 第三方SDK(被适配者)- 接口不兼容
public class StripeSdk
{
public StripeResponse CreateCharge(StripeChargeRequest request)
{
return new StripeResponse
{
Status = "succeeded",
Id = "ch_" + Guid.NewGuid().ToString("N")[..8],
Error = null
};
}
public StripeResponse CreateRefund(string chargeId, long amountCents)
{
return new StripeResponse
{
Status = "succeeded",
Id = "re_" + Guid.NewGuid().ToString("N")[..8],
Error = null
};
}
}
public class StripeChargeRequest
{
public long AmountCents { get; set; }
public string Currency { get; set; }
public string Description { get; set; }
}
public class StripeResponse
{
public string Status { get; set; }
public string Id { get; set; }
public string Error { get; set; }
}
// 适配器
public class StripeAdapter : IPaymentGateway
{
private readonly StripeSdk _stripe;
public StripeAdapter(StripeSdk stripe)
{
_stripe = stripe;
}
public PaymentResult Charge(string orderId, decimal amount, string currency)
{
// 适配:元→分,接口格式转换
var request = new StripeChargeRequest
{
AmountCents = (long)(amount * 100),
Currency = currency.ToLower(),
Description = $"Order: {orderId}"
};
var response = _stripe.CreateCharge(request);
return new PaymentResult
{
Success = response.Status == "succeeded",
TransactionId = response.Id,
Message = response.Error ?? "Payment successful"
};
}
public PaymentResult Refund(string transactionId, decimal amount)
{
var response = _stripe.CreateRefund(transactionId, (long)(amount * 100));
return new PaymentResult
{
Success = response.Status == "succeeded",
TransactionId = response.Id,
Message = response.Error ?? "Refund successful"
};
}
}
// 客户端调用
public class Program
{
public static void Main()
{
// 客户端只依赖IPaymentGateway接口
IPaymentGateway gateway = new StripeAdapter(new StripeSdk());
var result = gateway.Charge("ORD-001", 99.99m, "CNY");
Console.WriteLine($"支付{(result.Success ? "成功" : "失败")}: {result.TransactionId}");
var refund = gateway.Refund(result.TransactionId, 50.00m);
Console.WriteLine($"退款{(refund.Success ? "成功" : "失败")}: {refund.TransactionId}");
}
}
场景2: 遗留系统包装(Legacy System Wrapper)
将老旧系统的接口包装为现代接口,使新系统可以无缝调用遗留功能。
using System;
using System.Threading.Tasks;
// 现代接口(目标)
public interface IInventoryService
{
Task<InventoryResult> GetStockAsync(string sku);
Task<InventoryResult> UpdateStockAsync(string sku, int quantity);
Task<bool> IsAvailableAsync(string sku);
}
public class InventoryResult
{
public bool Success { get; set; }
public string Sku { get; set; }
public int Quantity { get; set; }
public string Message { get; set; }
}
// 遗留系统(被适配者)
public class LegacyInventorySystem
{
public string QueryInventory(string productCode)
{
// 模拟遗留系统返回格式:"SKU:数量:状态"
return $"{productCode}:150:OK";
}
public int AdjustInventory(string productCode, int delta)
{
// 模拟遗留系统返回新库存量
Console.WriteLine($"[Legacy] Adjusting {productCode} by {delta}");
return 150 + delta; // 返回调整后的数量
}
}
// 适配器
public class LegacyInventoryAdapter : IInventoryService
{
private readonly LegacyInventorySystem _legacy;
public LegacyInventoryAdapter(LegacyInventorySystem legacy)
{
_legacy = legacy;
}
public async Task<InventoryResult> GetStockAsync(string sku)
{
return await Task.Run(() =>
{
var raw = _legacy.QueryInventory(sku);
var parts = raw.Split(':');
return new InventoryResult
{
Success = parts[2] == "OK",
Sku = parts[0],
Quantity = int.Parse(parts[1]),
Message = parts[2]
};
});
}
public async Task<InventoryResult> UpdateStockAsync(string sku, int quantity)
{
return await Task.Run(() =>
{
var current = _legacy.QueryInventory(sku);
var currentQty = int.Parse(current.Split(':')[1]);
var delta = quantity – currentQty;
var newQty = _legacy.AdjustInventory(sku, delta);
return new InventoryResult
{
Success = true,
Sku = sku,
Quantity = newQty,
Message = "Stock updated"
};
});
}
public async Task<bool> IsAvailableAsync(string sku)
{
var result = await GetStockAsync(sku);
return result.Success && result.Quantity > 0;
}
}
// 客户端调用
public class Program
{
public static async Task Main()
{
IInventoryService service = new LegacyInventoryAdapter(new LegacyInventorySystem());
var stock = await service.GetStockAsync("SKU-12345");
Console.WriteLine($"库存查询: {stock.Sku} = {stock.Quantity}件 ({stock.Message})");
var available = await service.IsAvailableAsync("SKU-12345");
Console.WriteLine($"是否有货: {available}");
}
}
场景3: 数据格式转换(XML to JSON Conversion)
将输出XML格式的遗留数据源适配为返回JSON格式的现代数据接口。
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Text.Json;
// 目标接口 – JSON风格数据源
public interface IJsonDataSource
{
List<Dictionary<string, object>> Query(string collection);
Dictionary<string, object> GetById(string collection, string id);
}
// 被适配者 – XML数据源
public class XmlDataSource
{
public string GetXmlData(string tableName)
{
// 模拟XML数据
if (tableName == "employees")
{
return @"<employees>
<employee id=""E001"">
<name>张三</name>
<department>技术部</department>
<salary>15000</salary>
</employee>
<employee id=""E002"">
<name>李四</name>
<department>产品部</department>
<salary>18000</salary>
</employee>
<employee id=""E003"">
<name>王五</name>
<department>技术部</department>
<salary>16500</salary>
</employee>
</employees>";
}
return "<data/>";
}
}
// 适配器
public class XmlToJsonAdapter : IJsonDataSource
{
private readonly XmlDataSource _xmlSource;
public XmlToJsonAdapter(XmlDataSource xmlSource)
{
_xmlSource = xmlSource;
}
public List<Dictionary<string, object>> Query(string collection)
{
var xml = _xmlSource.GetXmlData(collection);
var doc = XDocument.Parse(xml);
var result = new List<Dictionary<string, object>>();
foreach (var element in doc.Root.Elements())
{
var dict = new Dictionary<string, object>
{
["id"] = element.Attribute("id")?.Value
};
foreach (var child in element.Elements())
{
dict[child.Name.LocalName] = child.Value;
}
result.Add(dict);
}
return result;
}
public Dictionary<string, object> GetById(string collection, string id)
{
var all = Query(collection);
return all.Find(d => d["id"]?.ToString() == id);
}
}
// 客户端调用
public class Program
{
public static void Main()
{
IJsonDataSource source = new XmlToJsonAdapter(new XmlDataSource());
var employees = source.Query("employees");
Console.WriteLine("=== 全部员工(JSON格式)===");
Console.WriteLine(JsonSerializer.Serialize(employees, new JsonSerializerOptions { WriteIndented = true }));
var emp = source.GetById("employees", "E002");
Console.WriteLine($"\\n=== 查找 E002 ===");
Console.WriteLine(JsonSerializer.Serialize(emp, new JsonSerializerOptions { WriteIndented = true }));
}
}
✅ 优点
- 接口兼容:让不兼容的接口可以协同工作
- 复用性好:可以复用已有的类,无需修改源码
- 符合开闭原则:通过新增适配器来支持新接口,不修改已有代码
- 客户端透明:客户端不需要知道适配器的存在
❌ 缺点
- 增加复杂度:引入了额外的类和间接层
- 性能开销:适配器增加了调用的间接层,可能有微小性能损耗
- 调试困难:链式适配器调用增加了调试的复杂性
📊 与其他模式对比
| 桥接模式 | 桥接是事前设计,分离抽象与实现;适配器是事后补救 |
| 装饰器模式 | 装饰器增强功能,适配器转换接口 |
| 代理模式 | 代理控制访问,适配器转换接口 |
| 外观模式 | 外观简化子系统接口,适配器转换已有接口 |






