如何在IObject接口实现中安全设置IsReferenced属性(支持Mock)
针对你遇到的封装性与可测试性冲突问题,这里有几种无需开放公共setter的可行方案:
1. 内部接口方案(优先推荐)
定义一个仅内部可见的接口,专门用于暴露可写的IsReferenced属性,让Object类同时实现公共接口IObject和这个内部接口。这样外部代码无法访问到可写的属性入口,同时Mock框架可以通过测试项目的权限设置来访问内部接口,实现可测试性。
代码示例:
// 对外公开的只读接口 public interface IObject { bool IsReferenced { get; } void Link(IObject other); } // 仅内部可见的可写接口 internal interface IInternalObject { bool IsReferenced { set; } } public class Object : IObject, IInternalObject { public bool IsReferenced { get; private set; } // 显式实现内部接口,外部无法直接访问 bool IInternalObject.IsReferenced { set => IsReferenced = value; } public void Link(IObject other) { if (other is IInternalObject internalOther) { internalOther.IsReferenced = true; } // 执行其他链接逻辑... } }
测试时,只需在项目的AssemblyInfo.cs或通过InternalsVisibleTo特性,将内部成员访问权限开放给测试项目和Mock框架(比如Moq),就能让Mock对象实现IInternalObject来模拟属性设置。
2. 受保护内部Setter(适用于允许继承的场景)
如果Object类允许被继承,可以将IsReferenced的setter设为protected internal,这样只有同程序集内的代码或子类可以修改属性。在Link方法中,将接口实例转换为具体类来设置属性,测试时可以MockObject子类或者直接Mock具体类。
代码示例:
public interface IObject { bool IsReferenced { get; } void Link(IObject other); } public class Object : IObject { public bool IsReferenced { get; protected internal set; } public void Link(IObject other) { if (other is Object concreteObj) { concreteObj.IsReferenced = true; } // 执行其他链接逻辑... } }
这种方案的局限性在于,如果Object是密封类,或者需要严格Mock接口而非具体类,就不太适用。
3. 回调委托方案(轻量场景可选)
在IObject内部添加一个用于设置属性的回调委托,作为内部成员或者显式实现的内部方法。不过这种方式会增加接口的复杂度,仅适合轻量场景。
代码示例:
public interface IObject { bool IsReferenced { get; } void Link(IObject other); // 内部显式实现的回调方法 internal void SetIsReferenced(bool value); } public class Object : IObject { public bool IsReferenced { get; private set; } void IObject.SetIsReferenced(bool value) { IsReferenced = value; } public void Link(IObject other) { ((IObject)other).SetIsReferenced(true); // 执行其他链接逻辑... } }
同样需要通过InternalsVisibleTo开放测试权限,相比内部接口方案,可读性和扩展性稍差。
总结
内部接口方案是最优选择,既能保证外部代码无法修改IsReferenced属性,又能通过测试权限配置支持MockIObject,完美平衡封装性和可测试性。
内容的提问来源于stack exchange,提问作者wforl

