可变类型的不可变的意见 [英] Immutable views of mutable types

查看:129
本文介绍了可变类型的不可变的意见的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个项目,我需要构建的配置数据相当数量之前,我可以执行的过程。在配置阶段,这是非常方便的将数据作为可变的。然而,一旦配置完成,我想传递数据的不可变视图功能的过程中,作为该进程将依赖于配置的不可变性为它的许多计算(例如,能力pre-根据初始配置计算的事情。)我一直使用接口暴露一个只读视图拿出一个可行的解决方案,但我想知道是否有人遇到问题,这种类型的方法或者有其他建议对于如何解决这个问题。

I have a project where I need to construct a fair amount of configuration data before I can execute a process. During the configuration stage, it's very convenient to have the data as mutable. However, once configuration has been completed, I'd like to pass an immutable view of that data to the functional process, as that process will rely on configuration immutability for many of its computations (for instance, the ability to pre-compute things based on initial configuration.) I've come up with a possible solution using interfaces to expose a read-only view, but I'd like to know if anybody has encountered problems with this type of approach or if there are other recommendations for how to solve this problem.

对我目前使用该模式的一个例子:

One example of the pattern I'm currently using:

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>();
    }
}


修改


EDIT

,我放在一起以下(去掉了一些特性,以简化):

Based on input from Mr. Lippert and cdhowie, I put together the following (removed some properties to simplify):

[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&LT; T&GT; ,正如你所期望的,可冷冻实施中的IList&LT; T&GT; 。此获得绝缘的好处,在一些额外的复杂性的费用。

FreezableList<T> is, as you would expect, a freezable implementation of IList<T>. This gains insulation benefits, at the cost of some additional complexity.

推荐答案

您所描述的作品的做法巨大的,如果客户(该接口的消费者)和服务器(类的提供者)具有共同的一致认为:

The approach you describe works great if the "client" (the consumer of the interface) and the "server" (the provider of the class) have a mutual agreement that:

  • 客户端将礼貌,不要试图利用服务器的实现细节
  • 优势
  • 服务器将是礼貌,没有发生变异后,客户端的对象都有一个参考吧。

如果您还没有人编写客户端和人民写的服务器,然后事物之间的良好工作关系去梨形快。当然是一个粗鲁的客户端可以抛弃的不变性通过转换成公众配置类型。一个粗鲁的服务器可以把手伸到一个不可改变的视图,然后发生变异的对象时,客户至少期望它。

If you do not have a good working relationship between the people writing the client and the people writing the server then things go pear-shaped quickly. A rude client can of course "cast away" the immutability by casting to the public Configuration type. A rude server can hand out an immutable view and then mutate the object when the client least expects it.

一个很好的方法是prevent从曾经看到的可变类型的客户端:

A nice approach is to prevent the client from ever seeing the mutable type:

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。当你完成你的突变,可以调用完成并获得一个只读接口。 (然后生成器变为无效。)由于所有的可变性的实现细节都隐藏在一个私人嵌套类,你不能抛弃的IReadOnly接口RealFrobber,只有Frobber,它没有公开的方法!

Now if you want to create and mutate a Frobber, you can make a Frobber.FrobBuilder. When you're done your mutations, you call Complete and get a read-only interface. (And then the builder becomes invalid.) Since all the mutability implementation details are hidden in a private nested class, you can't "cast away" the IReadOnly interface to RealFrobber, only to Frobber, which has no public methods!

也可以在恶劣的客户创建自己的Frobber,因为Frobber是抽象的和私人的构造。做一个Frobber的唯一方法是通过生成器。

Nor can the hostile client create their own Frobber, because Frobber is abstract and has a private constructor. The only way to make a Frobber is via the builder.

这篇关于可变类型的不可变的意见的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆