You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不可变对象引用更新:如何同步关联对象的属性变更?

解决方案:避免Customer变更同步遗漏的两种思路

一、更优的替代方案:间接引用代理模式

既然Customer是不可变类,我们可以放弃“修改原Customer实例”的思路,转而通过代理类间接持有Customer引用——所有需要关联Customer的业务类(Order、Invoice等)都持有这个代理,而非直接持有Customer实例。当客户信息变更时,创建新的Customer实例并更新代理内的引用,所有关联类会自动获取到最新数据,根本不需要事件通知。

代码示例

// 不可变Customer类(保持原有设计)
public sealed class Customer
{
    public string Id { get; }
    public string PhoneNumber { get; }

    public Customer(string id, string phoneNumber)
    {
        Id = id;
        PhoneNumber = phoneNumber;
    }

    // 不可变类的属性修改方式:返回新实例
    public Customer WithPhoneNumber(string newPhone)
    {
        return new Customer(Id, newPhone);
    }
}

// 代理类:维护可变的Customer引用
public sealed class CustomerReference
{
    private Customer _currentCustomer;

    public CustomerReference(Customer initialCustomer)
    {
        _currentCustomer = initialCustomer;
    }

    // 对外暴露当前有效的Customer实例
    public Customer Current => _currentCustomer;

    // 仅允许更新同ID的Customer实例
    public void UpdateCustomer(Customer newCustomer)
    {
        if (newCustomer.Id != _currentCustomer.Id)
            throw new ArgumentException("只能更新同一客户的实例");
        _currentCustomer = newCustomer;
    }
}

// Order类:持有代理而非直接持有Customer
public class Order
{
    private readonly CustomerReference _customerRef;

    public Order(CustomerReference customerRef)
    {
        _customerRef = customerRef;
    }

    public string GetCustomerPhone()
    {
        // 每次获取都从代理拿最新实例
        return _customerRef.Current.PhoneNumber;
    }
}

// Invoice类:复用代理逻辑,无需额外修改
public class Invoice
{
    private readonly CustomerReference _customerRef;

    public Invoice(CustomerReference customerRef)
    {
        _customerRef = customerRef;
    }

    public string GetCustomerContact()
    {
        return _customerRef.Current.PhoneNumber;
    }
}

使用方式

// 初始化客户及代理
var initialCustomer = new Customer("C001", "13800138000");
var customerRef = new CustomerReference(initialCustomer);

// 创建关联业务对象
var order = new Order(customerRef);
var invoice = new Invoice(customerRef);

// 更新客户手机号:创建新实例+更新代理
var updatedCustomer = initialCustomer.WithPhoneNumber("13900139000");
customerRef.UpdateCustomer(updatedCustomer);

// Order和Invoice自动获取最新手机号
Console.WriteLine(order.GetCustomerPhone()); // 输出13900139000
Console.WriteLine(invoice.GetCustomerContact()); // 输出13900139000

这个方案的核心优势:新增任何关联Customer的业务类时,只要持有CustomerReference就自动支持同步更新,完全不会出现遗漏事件注册的问题,同时契合不可变对象的设计原则。

二、若坚持使用观察者模式:编译器/启动阶段的检测方案

如果必须基于观察者模式实现,可通过接口强制+自动注册+静态代码分析的组合,在编译或应用启动阶段检测遗漏的事件订阅。

1. 定义强制接口

让所有需要监听Customer变更的类必须实现ICustomerObserver接口:

public interface ICustomerObserver
{
    void OnCustomerUpdated(Customer oldCustomer, Customer newCustomer);
}

// Order实现接口
public class Order : ICustomerObserver
{
    private Customer _customer;

    public Order(Customer customer)
    {
        _customer = customer;
    }

    public void OnCustomerUpdated(Customer oldCustomer, Customer newCustomer)
    {
        if (_customer.Id == oldCustomer.Id)
            _customer = newCustomer;
    }
}

2. 依赖注入自动注册

利用DI容器扫描所有实现ICustomerObserver的类型,自动完成事件注册(以.NET Autofac为例):

var builder = new ContainerBuilder();
// 注册所有ICustomerObserver实现类
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
       .Where(t => typeof(ICustomerObserver).IsAssignableFrom(t) && !t.IsInterface)
       .As<ICustomerObserver>();

var container = builder.Build();
// 批量注册事件
var observers = container.Resolve<IEnumerable<ICustomerObserver>>();
foreach (var observer in observers)
{
    Customer.CustomerUpdated += observer.OnCustomerUpdated;
}

新增业务类时,只要实现ICustomerObserver,DI容器就会自动完成事件注册,避免手动修改事件处理器的遗漏。

3. 静态代码分析/单元测试检测

编写自定义静态代码分析规则(如C# Roslyn分析器),或者单元测试,检测:

  • 所有持有Customer引用的类,是否都实现了ICustomerObserver接口
  • Customer的事件注册逻辑是否包含了所有ICustomerObserver实现类

比如单元测试示例(反射实现):

[Test]
public void AllCustomerDependentTypesImplementObserver()
{
    var assembly = Assembly.GetExecutingAssembly();
    // 找到所有持有Customer字段/属性的类
    var customerDependentTypes = assembly.GetTypes()
        .Where(t => t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
                     .Any(f => f.FieldType == typeof(Customer))
                    || t.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
                     .Any(p => p.PropertyType == typeof(Customer)));

    // 检查每个类是否实现了ICustomerObserver
    var missingObserverTypes = customerDependentTypes
        .Where(t => !typeof(ICustomerObserver).IsAssignableFrom(t))
        .ToList();

    Assert.IsEmpty(missingObserverTypes, 
        $"以下类持有Customer引用但未实现ICustomerObserver:{string.Join(", ", missingObserverTypes.Select(t => t.Name))}");
}

这个测试会在运行时检测遗漏的类,若有未实现接口的情况直接报错。


内容的提问来源于stack exchange,提问作者Viktor Plaček

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:25:54