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

发布于 2024-09-16 01:32:52 字数 2387 浏览 6 评论 0原文

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

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

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

然而,我不确定是否正确设置了 IMutableIItem 的关系。现在,我有每个接口(IWidgetIDooHickey)继承自IItem,然后是可变类(Widget, DooHickey) 此外还实现了 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();
    }
}

In my current project I need to be able to have both editable and read-only versions of classes. So that when the classes are displayed in a List or PropertGrid the user is not able to edit objects they should not be allowed to.

To do this I'm following the design pattern shown in the diagram below. I start with a read-only interface (IWidget), and then create an edtiable class which implements this interface (Widget). Next I create a read-only class (ReadOnlyWidget) which simply wraps the mutable class and also implements the read only interface.

I'm following this pattern for a number of different unrelated types. But now I want to add a search function to my program, which can generate results that include any variety of types including both mutable and immutable versions. So now I want to add another set of interfaces (IItem, IMutableItem) that define properties which apply to all types. So IItem defines a set of generic immutable properties, and IMutableItem defines the same properties but editable. In the end a search will return a collection of IItems, which can then later be cast to more specific types if needed.

Yet, I'm not sure if I'm setting up the relationships to IMutable and IItem correctly. Right now I have each of the interfaces (IWidget, IDooHickey) inheriting from IItem, and then the mutable classes (Widget, DooHickey) in addition also implement IMutableItem.

Alternatively, I was also thinking I could then set IMutableItem to inherit from IItem, which would hide its read-only properties with new properties that have both get and set accessors. Then the mutable classes would implement IMutableItem, and the read-only classes would implement IItem.

I'd appreciate any suggestions or criticisms regarding any of this.

Class Diagram

alt text

Code

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

黄昏下泛黄的笔记 2024-09-23 01:32:52

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

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

Is it OK to create another object when you need a readonly copy? If so then you can use the technique in the included code. If not, I think a wrapper is probably your best bet when it comes to this.

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);
    }
}
小红帽 2024-09-23 01:32:52

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

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

所有这一切的最大困难是,如果尝试使用类类型而不是接口,继承就不能很好地工作。例如,如果希望有一个从 BasicCustomer 继承的 EnhancedCustomer 类,则 ImmutableEnhancedCustomer 应该从 ImmutableBasicCustomer 继承code> 和 ReadableEnhancedCustomer,但 .net 不允许这种双重继承。人们可以使用接口 IImmutableEnhancedCustomer 而不是类,但有些人会认为“不可变接口”有点奇怪,因为模块不可能以这样的方式定义接口:外部人员可以使用它,而无需允许外部人员定义自己的实现。

I would suggest that for each main class or interface, there be three defined classes: a "readable" class, a "changeable" class, and an "immutable" class. Only the "changeable" or "immutable" classes should exist as concrete types; they should both derive from an abstract "readable" class. Code which wants to store an object secure in the knowledge that it never changes should store the "immutable" class; code that wants to edit an object should use the "changeable" class. Code which isn't going to write to something but doesn't care if it holds the same value forever can accept objects of the "readable" base type.

The readable version should include public abstract methods AsChangeable(), AsImmutable(), public virtual method AsNewChangeable(), and protected virtual method AsNewImmutable(). The "changeable" classes should define AsChangeable() to return this, and AsImmutable to return AsNewImmutable(). The "immutable" classes should define AsChangeable() to return AsNewChangeable() and AsImmutable() to return this.

The biggest difficulty with all this is that inheritance doesn't work terribly well if one tries to use class types rather than interfaces. For example, if one would like to have an EnhancedCustomer class which inherits from BasicCustomer, then ImmutableEnhancedCustomer should inherit from both ImmutableBasicCustomer and ReadableEnhancedCustomer, but .net doesn't allow such dual inheritance. One could use an interface IImmutableEnhancedCustomer rather than a class, but some people would consider an 'immutable interace' to be a bit of a smell since there's no way a module that defines an interface in such a way that outsiders can use it without also allowing outsiders to define their own implementations.

忆伤 2024-09-23 01:32:52

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

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

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

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

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

Abandon hope all ye who enter here!!!

I suspect that in the long run your code is going to be very confusing. Your class diagram suggests that all properties are editable (or not) in a given object. Or are your (I'm)mutable interfaces introducing new properties that are all immutable or not, separate from the "core"/inheriting class?

Either way I think you're going to end up with playing games with property name variations and/or hiding inherited properties

Marker Interfaces Perhaps?
Consider making all properties in your classes mutable. Then implement IMutable (I don't like the name IItem) and IImutable as a marker interfaces. That is, there is literally nothing defined in the interface body. But it allows client code to handle the objects as a IImutable reference, for example.

This implies that either (a) your client code plays nice and respects it's mutability, or (b) all your objects are wrapped by a "controller" class that enforces the given object's mutability.

沒落の蓅哖 2024-09-23 01:32:52

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

Could be too late :-), but the cause "The keyword 'new' is required on property because it hides property ..." is a bug in Resharper, no problem with the compiler. See the example below:

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

Same for the case without interfaces. The only limitation, you can't use auto-properties

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文