在复杂对象结构中创建只读版本的类

时间:2010-08-27 01:22:51

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

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

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

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

然而,我不确定我是否正确设置了与IMutableIItem的关系。现在我将每个接口(IWidgetIDooHickey)继承自IItem,然后是可变类(WidgetDooHickey)另外实施IMutableItem

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

我很感激有关这方面的任何建议或批评。

类图

alt text

代码

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

4 个答案:

答案 0 :(得分: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);
    }
}

答案 1 :(得分:1)

我建议对于每个主类或接口,有三个定义的类:“可读”类,“可更改”类和“不可变”类。只有“可变”或“不可变”的类应该作为具体类型存在;它们都应该来自抽象的“可读”类。希望在永不改变的知识中存储对象的代码应该存储“不可变”类;想要编辑对象的代码应使用“可更改”类。代码不会写入某些内容但不关心它是否永远保持相同的值可以接受“可读”基类型的对象。

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

所有这一切的最大困难在于,如果尝试使用类类型而不是接口,则继承不能很好地工作。例如,如果想要一个继承自EnhancedCustomer的{​​{1}}类,那么BasicCustomer应该继承ImmutableEnhancedCustomerImmutableBasicCustomer,但.net不允许这种双重继承。可以使用接口ReadableEnhancedCustomer而不是类,但是有些人会认为“不可变的interace”有点气味,因为没有办法以外人可以使用的方式定义接口的模块它还允许外人定义自己的实现。

答案 2 :(得分:0)

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

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

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

标记界面也许?
考虑使中的所有属性都可变。然后实现IMutable(我不喜欢名称IItem)和IImutable作为标记接口。也就是说,界面体中没有任何定义。但它允许客户端代码将对象作为IImutable引用来处理,例如。

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

答案 3 :(得分:0)

可能为时已晚:-),但原因“关键字'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);
    }
}