【问题标题】:Creating read-only versions of classes in a complex object structure在复杂对象结构中创建类的只读版本
【发布时间】:2010-08-27 01:22:51
【问题描述】:

在我当前的项目中,我需要能够同时拥有可编辑和只读版本的类。因此,当类显示在 List 或 PropertGrid 中时,用户无法编辑不应被允许的对象。

为此,我遵循下图所示的设计模式。我从一个只读接口 (IWidget) 开始,然后创建一个实现该接口的可编辑类 (Widget)。接下来我创建一个只读类(ReadOnlyWidget),它简单地包装了可变类并实现了只读接口。

我对许多不同的不相关类型都遵循这种模式。但是现在我想在我的程序中添加一个搜索功能,它可以生成包括任何类型的结果,包括可变和不可变版本。所以现在我想添加另一组定义适用于所有类型的属性的接口(@98​​7654326@、IMutableItem)。所以IItem 定义了一组通用的不可变属性,IMutableItem 定义了相同的属性但可编辑。最后,搜索将返回 IItems 的集合,如果需要,以后可以将其转换为更具体的类型。

但是,我不确定我是否正确设置了与 IMutable 和 IItem 的关系。现在我的每个接口(@98​​7654333@,IDooHickey)都继承自IItem,然后可变类(Widget,DooHickey)还实现了IMutableItem。

另外,我还想我可以将IMutableItem 设置为从IItem 继承,这将使用具有get 和set 访问器的新属性隐藏其只读属性。然后可变类将实现IMutableItem,而只读类将实现IItem。

如果您对此提出任何建议或批评,我将不胜感激。

类图

代码

public interface IItem
{
    string ItemName { get; }
}

public interface IMutableItem
{
    string ItemName { get; set; }
}

public interface IWidget:IItem
{
    void Wiggle();
}

public abstract class Widget : IWidget, IMutableItem
{
    public string ItemName
    {
        get;
        set;
    }

    public void Wiggle()
    {
        //wiggle a little
    }
}

public class ReadOnlyWidget : IWidget
{
    private Widget _widget;
    public ReadOnlyWidget(Widget widget)
    {
        this._widget = widget;
    }

    public void Wiggle()
    {
        _widget.Wiggle();
    }

    public string ItemName
    {
        get {return  _widget.ItemName; }
    }
}

public interface IDoohickey:IItem
{
    void DoSomthing();
}


public abstract class Doohickey : IDoohickey, IMutableItem
{
    public void DoSomthing()
    {
        //work it, work it
    }

    public string ItemName
    {
        get;
        set;
    }
}

public class ReadOnlyDoohickey : IDoohickey
{
    private Doohickey _doohicky;
    public ReadOnlyDoohickey(Doohickey doohicky)
    {
        this._doohicky = doohicky;
    }

    public string ItemName
    {
        get { return _doohicky.ItemName; }
    }

    public void DoSomthing()
    {
        this._doohicky.DoSomthing();
    }
}

【问题讨论】:

  • 我一直在我的一个项目中使用您描述的替代方法(从 IItem 继承 IMutableItem 并在那里添加一个带有 get 和 set 的属性),并且效果很好。您也可以考虑将 SetXXX 之类的方法添加到 IMutableItem,而不是使用 setter 的新属性(请参阅stackoverflow.com/questions/623824/…)。

标签: c# .net interface class-design readonly


【解决方案1】:

当您需要只读副本时可以创建另一个对象吗?如果是这样,那么您可以在包含的代码中使用该技术。如果没有,我认为包装器可能是您最好的选择。

internal class Test
{
    private int _id;
    public virtual int ID
    {
        get
        {
            return _id;
        }
        set
        {
            if (ReadOnly)
            {
                throw new InvalidOperationException("Cannot set properties on a readonly instance.");
            }
        }
    }

    private string _name;
    public virtual string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if (ReadOnly)
            {
                throw new InvalidOperationException("Cannot set properties on a readonly instance.");
            }
        }
    }

    public bool ReadOnly { get; private set; }

    public Test(int id = -1, string name = null)
        : this(id, name, false)
    { }

    private Test(int id, string name, bool readOnly)
    {
        ID = id;
        Name = name;
        ReadOnly = readOnly;
    }

    public Test AsReadOnly()
    {
        return new Test(ID, Name, true);
    }
}

【讨论】:

    【解决方案2】:

    我建议对于每个主类或接口,定义三个类:一个“可读”类、一个“可变”类和一个“不可变”类。只有“可变”或“不可变”类应该作为具体类型存在;它们都应该派生自一个抽象的“可读”类。想要在知道对象永远不会更改的情况下安全地存储对象的代码应该存储“不可变”类;想要编辑对象的代码应该使用“可更改”类。不会写东西但不关心它是否永远保持相同值的代码可以接受“可读”基类型的对象。

    可读版本应包括公共抽象方法AsChangeable()、AsImmutable()、公共虚拟方法AsNewChangeable()和受保护的虚拟方法AsNewImmutable()。 “可变”类应该定义AsChangeable() 以返回this,并定义AsImmutable 以返回AsNewImmutable()。 “不可变”类应定义 AsChangeable() 以返回 AsNewChangeable() 和 AsImmutable() 以返回 this。

    所有这一切的最大困难是,如果尝试使用类类型而不是接口,继承就不能很好地工作。例如,如果想拥有一个继承自BasicCustomer 的EnhancedCustomer 类,那么ImmutableEnhancedCustomer 应该继承自ImmutableBasicCustomer 和ReadableEnhancedCustomer,但.net 不允许这种双重继承。可以使用接口IImmutableEnhancedCustomer 而不是类,但有些人会认为“不可变接口”有点异味,因为没有任何模块可以定义接口以使外人可以在没有的情况下使用它还允许外部人员定义自己的实现。

    【讨论】:

      【解决方案3】:

      放弃希望所有进入这里的人!!!

      我怀疑从长远来看,您的代码会非常混乱。您的类图表明 所有 属性在给定对象中是可编辑的(或不可编辑的)。或者您的(我)可变接口是否引入了与“核心”/继承类分开的所有不可变的新属性?

      无论哪种方式,我认为您最终都会玩具有属性名称变化和/或隐藏继承属性的游戏

      可能是标记接口?
      考虑让你的 classes 中的所有属性都是可变的。然后实现 IMutable(我不喜欢 IItem 这个名字)和 IImutable 作为标记接口。也就是说,接口主体中实际上没有定义任何内容。但它允许客户端代码将对象作为 IImutable 引用来处理。

      这意味着 (a) 您的客户端代码运行良好并尊重其可变性,或者 (b) 您的所有对象都被一个“控制器”类包装,该类强制执行给定对象的可变性。

      【讨论】:

        【解决方案4】:

        可能为时已晚:-),但原因“属性上需要关键字'new',因为它隐藏了属性......”是Resharper中的一个错误,编译器没有问题。请参见下面的示例:

        public interface IEntityReadOnly
        {
            int Prop { get; }
        }
        
        
        public interface IEntity : IEntityReadOnly
        {
            int Prop { set; }
        }
        
        public class Entity : IEntity
        {
            public int Prop { get; set; }
        }
        
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
                var entity = new Entity();
                (entity as IEntity).Prop = 2;
                Assert.AreEqual(2, (entity as IEntityReadOnly).Prop);
            }
        }
        

        对于没有接口的情况也是如此。唯一的限制,你不能使用自动属性

        public class User
        {
            public User(string userName)
            {
                this.userName = userName;
            }
        
            protected string userName;
            public string UserName { get { return userName; } }
        }
        
        public class UserUpdatable : User
        {
            public UserUpdatable()
                : base(null)
            {
            }
        
            public string UserName { set { userName = value; } }
        }
        
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
                var user = new UserUpdatable {UserName = "George"};
                Assert.AreEqual("George", (user as User).UserName);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-12-12
          • 1970-01-01
          • 1970-01-01
          • 2020-07-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多