我有一个项目,在执行流程之前,我需要构建大量配置数据。在配置阶段,将数据设置为可变的非常方便。但是,一旦配置完成,我想将该数据的不可变 View 传递给功能流程,因为该流程的许多计算都依赖于配置不变性(例如,基于预计算事物的能力)在初始配置上。)我想出了一个可能的解决方案,使用接口(interface)来公开只读 View ,但我想知道是否有人遇到过这种方法的问题,或者是否有其他建议如何解决这个问题。
我目前使用的模式的一个例子:
public interface IConfiguration
{
string Version { get; }
string VersionTag { get; }
IEnumerable<IDeviceDescriptor> Devices { get; }
IEnumerable<ICommandDescriptor> Commands { get; }
}
[DataContract]
public sealed class Configuration : IConfiguration
{
[DataMember]
public string Version { get; set; }
[DataMember]
public string VersionTag { get; set; }
[DataMember]
public List<DeviceDescriptor> Devices { get; private set; }
[DataMember]
public List<CommandDescriptor> Commands { get; private set; }
IEnumerable<IDeviceDescriptor> IConfiguration.Devices
{
get { return Devices.Cast<IDeviceDescriptor>(); }
}
IEnumerable<ICommandDescriptor> IConfiguration.Commands
{
get { return Commands.Cast<ICommandDescriptor>(); }
}
public Configuration()
{
Devices = new List<DeviceDescriptor>();
Commands = new List<CommandDescriptor>();
}
}
编辑
根据 Lippert 先生和 cdhowie 的意见,我整理了以下内容(删除了一些属性以简化):
[DataContract]
public sealed class Configuration
{
private const string InstanceFrozen = "Instance is frozen";
private Data _data = new Data();
private bool _frozen;
[DataMember]
public string Version
{
get { return _data.Version; }
set
{
if (_frozen) throw new InvalidOperationException(InstanceFrozen);
_data.Version = value;
}
}
[DataMember]
public IList<DeviceDescriptor> Devices
{
get { return _data.Devices; }
private set { _data.Devices.AddRange(value); }
}
public IConfiguration Freeze()
{
if (!_frozen)
{
_frozen = true;
_data.Devices.Freeze();
foreach (var device in _data.Devices)
device.Freeze();
}
return _data;
}
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
_data = new Data();
}
private sealed class Data : IConfiguration
{
private readonly FreezableList<DeviceDescriptor> _devices = new FreezableList<DeviceDescriptor>();
public string Version { get; set; }
public FreezableList<DeviceDescriptor> Devices
{
get { return _devices; }
}
IEnumerable<IDeviceDescriptor> IConfiguration.Devices
{
get { return _devices.Select(d => d.Freeze()); }
}
}
}
FreezableList<T>
如您所料,是 IList<T>
的可卡住实现.这获得了绝缘优势,但代价是增加了一些额外的复杂性。
最佳答案
如果“客户端”(接口(interface)的使用者)和“服务器”(类的提供者)达成以下共识,那么您描述的方法非常有效:
- 客户端会保持礼貌,不会试图利用服务器的实现细节
- 服务器会礼貌地在客户端引用对象后不会改变对象。
如果您在编写客户端的人员和编写服务器的人员之间没有良好的工作关系,那么事情很快就会变得糟糕。粗鲁的客户当然可以通过转换为公共(public)配置类型来“抛弃”不变性。一个粗鲁的服务器可以分发一个不可变的 View ,然后在客户端最不期望的时候改变对象。
一个好的方法是防止客户端看到可变类型:
public interface IReadOnly { ... }
public abstract class Frobber : IReadOnly
{
private Frobber() {}
public class sealed FrobBuilder
{
private bool valid = true;
private RealFrobber real = new RealFrobber();
public void Mutate(...) { if (!valid) throw ... }
public IReadOnly Complete { valid = false; return real; }
}
private sealed class RealFrobber : Frobber { ... }
}
现在如果你想创建和变异一个 Frobber,你可以创建一个 Frobber.FrobBuilder。完成变更后,调用 Complete 并获得一个只读界面。 (然后构建器变得无效。)由于所有可变性实现细节都隐藏在私有(private)嵌套类中,因此您不能“丢弃”RealFrobber 的 IReadOnly 接口(interface),只能丢弃没有公共(public)方法的 Frobber!
恶意客户端也不能创建自己的 Frobber,因为 Frobber 是抽象的并且有私有(private)构造函数。制作 Frobber 的唯一方法是通过构建器。
关于c# - 可变类型的不可变 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4168382/