不修改MoneyAdder类如何适配需要客户名参数的OldLogger组件
思路与术语指正
- 你前面3个适配器的实现完全符合对象适配器模式的规范,也遵循了开闭原则,术语使用正确。
- 若OldLogger原本是直接在MoneyAdder内部硬编码实例化调用,确实属于紧耦合(Tightly Coupled),你的判断正确。
- 你之前的认知存在误区:除了方法入参、可计算值、全局信息外,还可以通过组合根(Composition Root)的依赖注入上下文为具体实现传递额外参数,完全不需要修改目标类的代码。
适配OldLogger的方案
方案1:DI上下文工厂注入(推荐,无性能损耗,符合设计规范)
这个方案的核心是:MoneyAdder的构造本身就需要传入Customer实例,你可以在组装MoneyAdder的组合根位置,提前将对应Customer的Name传入OldLoggerAdapter的构造函数,不需要修改ILogger接口和MoneyAdder的任何代码。
代码实现
首先改造OldLoggerAdapter,将客户名作为构造参数传入:
public class OldLoggerAdapter : ILogger { private readonly string _customerName; private readonly OldLogger _oldLogger = new OldLogger(); // 构造函数接收客户名,不需要修改ILogger接口定义 public OldLoggerAdapter(string customerName) { _customerName = customerName; } public void log(string str) { _oldLogger.log(str, _customerName); } }
然后在你创建MoneyAdder的位置(也就是程序的组合根)组装实例即可:
// 该部分属于你的程序启动/实例组装逻辑,完全不需要修改MoneyAdder的源码 var currentCustomer = GetCustomerFromRequest(); // 你原本就要获取Customer实例传入MoneyAdder var logger = new OldLoggerAdapter(currentCustomer.Name); var moneyAdder = new MoneyAdder(logger, currentCustomer);
如果使用DI容器(比如.NET自带的DI、Autofac等),可以通过Scope工厂注册实现自动注入:
// .NET 自带DI注册示例 services.AddScoped<MoneyAdder>(sp => { var customer = sp.GetRequiredService<Customer>(); // 取当前Scope下的Customer实例 var logger = new OldLoggerAdapter(customer.Name); return new MoneyAdder(logger, customer); });
方案2:反射获取调用方私有字段(不推荐,仅作方案补充)
如果确实无法在组合根处理参数传递,可以通过调用栈反射获取MoneyAdder实例的私有Customer字段,注意该方案有性能损耗,且依赖MoneyAdder的内部结构,一旦MoneyAdder内部字段改名就会失效。
代码实现
using System.Diagnostics; using System.Reflection; public class OldLoggerAdapter : ILogger { private readonly OldLogger _oldLogger = new OldLogger(); private static readonly FieldInfo _customerField = typeof(MoneyAdder).GetField("customer", BindingFlags.Instance | BindingFlags.NonPublic); public void log(string str) { // 获取调用方的MoneyAdder实例 var callFrame = new StackFrame(1); if (callFrame.GetMethod().DeclaringType != typeof(MoneyAdder)) { throw new InvalidOperationException("该日志实现仅支持MoneyAdder调用"); } var moneyAdderInstance = callFrame.This; // 反射拿Customer字段的值 var customer = (Customer)_customerField.GetValue(moneyAdderInstance); _oldLogger.log(str, customer.Name); } }
额外优化建议
- 你之前的适配器实现中直接硬编码
new OtherLogger()/new StrangeLogger(),可以将第三方日志实例改为适配器的构造参数,由DI容器注入,更方便替换和单元测试。 - 接口命名建议遵循C#规范,方法名首字母大写,比如
ILogger的Log方法,不过这个属于编码规范问题,不影响设计逻辑。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

