代码之家  ›  专栏  ›  技术社区  ›  Stefan

如何在C#/Java中针对const的不足进行设计?

  •  3
  • Stefan  · 技术社区  · 15 年前

    在尝试建立我的域模型时,我遇到了以下问题。想象一下我们有一件事:

    class Thing
    {
        public int X { get; set; }
    }
    

    东西有一个属性X。然后,有一个包,用来聚合东西。但是域要求包可以容纳的东西有一些限制。例如,Xes的累积值不能高于某个特定限制:

    class Pack
    {
        private readonly List<Thing> myThings = new List<Thing>();
        private const int MaxValue = 5;
    
        public void Add(Thing thing)
        {
            if (myThings.Sum(t => t.X) + thing.X > MaxValue)
                throw new Exception("this thing doesn't fit here");
            myThings.Add(thing);
        }
    
        public int Count
        {
            get { return myThings.Count; }
        }
    
        public Thing this[int index]
        {
            get { return myThings[index]; }
        }
    }
    

    所以我在给包里装东西之前先检查一下情况,但还是很容易惹上麻烦:

    var pack = new Pack();
    pack.Add(new Thing { X = 2 });
    pack.Add(new Thing { X = 1 });
    
    var thingOne = new Thing { X = 1 };
    var thingTwo = new Thing { X = 3 };
    
    //pack.Add(thingTwo); // exception
    pack.Add(thingOne);   // OK
    
    thingOne.X = 5;       // trouble
    pack[0].X = 10;       // more trouble
    

    在C++中,解决方案是在索引器中插入和返回const引用时复制。如何在C(可能还有Java)中围绕这个问题进行设计?我只是想不出一个好的解决办法:

    1. 使事物不变-但如果它需要可变呢?
    2. 用事件/观察者来观察包中的东西——但这意味着包强制设计了东西;如果东西有更多的属性呢?然后,我将只结束一个事件,因为需要包看变化-这似乎是尴尬的我。

    有什么想法或首选的解决方案吗?

    编辑:

    回到这个问题。。。我已经接受了我的答复。他说得对。

    很好,在C++中,这个特殊的东西可以用const修饰符来处理。如果你想让事情保持一致的状态的话,代码看起来要少得多。

    7 回复  |  直到 14 年前
        1
  •  6
  •   Itay Karo    15 年前

    制造 Thing 不变的。

    class Thing
    {
        public Thing (int x)
        {
           X = x;
        }
        public int X { get; private set; }
    }
    

    另外,代替 if (myThings.Sum(t => t.X) + thing.X > MaxValue) 我认为最好把sum字段放在pack中,这样就不必每次都重新计算sum。

    编辑
    对不起,我错过了你说你需要可变的事实。
    但是。。。你的c++解决方案是如何工作的?我不太懂c++但是c++常量引用不会阻止包上实例的更改吗?

    编辑2
    使用接口

    public interface IThing
    {
      int X { get; }
    }
    
    public class Thing : IThing
    {
      int X { get; set; }
    }
    
    class Pack
    {
        private readonly List<IThing> myThings = new List<IThing>();
        private const int MaxValue = 5;
    
        public void Add(IThing thing)
        {
            if (myThings.Sum(t => t.X) + thing.X > MaxValue)
                throw new Exception("this thing doesn't fit here");
            myThings.Add(new InnerThing(thing));
        }
    
        public int Count
        {
            get { return myThings.Count; }
        }
    
        public IThing this[int index]
        {
            get { return myThings[index]; }
        }
    
        private class InnerThing : IThing
        {
          public InnerThing(IThing thing)
          {
            X = thing.X;
          }
          int X { get; private set; }
        }
    }
    
        2
  •  3
  •   Frosty840    15 年前

    您可以让Thing实现接口iReadOnlyThing,它只对每个Thing的属性具有ReadOnly访问权限。

    你可以用同样的方法实现一个只读容器类

    System.Collections.ObjectModel.ObservableCollection<T>
    

    System.Collections.ObjectModel.ReadOnlyObservableCollection<T>
    

    去吧。ReadOnlyObservableCollection的构造函数以ObservableCollection为参数,允许对集合进行读取访问,而不允许进行写入访问。它的优点是不允许readonlyobservatablecollection被转换回observatedcollection。

    我觉得我应该指出,WPF开发中使用的Model-View-ViewModel模式实际上是按照您建议的原则工作的,即拥有一个NotifyPropertyChanged事件,该事件包含一个标识更改的属性的字符串。。。

    MVVM模式的编码开销很大,但有些人似乎喜欢它。。。

        3
  •  2
  •   chiccodoro    15 年前

    根据你的陈述,你在 X 也必须适用于 准备好了。你应该仔细考虑你的设计 如果它应该触发一些业务逻辑。它不仅仅是一种财产。

    您可以考虑以下解决方案:

    • 你真的需要允许改变吗 ? 即使类不是不可变的,您仍然可以 只读。
    • 提供 SetX(...) 方法,该方法封装业务逻辑以检查 Thing Pack 然后叫它 的验证逻辑。
    • 当然,您可以使用属性而不是setter方法 set { } 同样的道理。
    • Pack.Add ,创建 事情 ,它实际上是添加验证逻辑的子类。
    • ...

    不管你做什么,你都不会回避以下非常基本的设计问题:要么你允许更改X,然后你必须在允许更改X的每个部分中引入验证/业务逻辑;要么你将X设为只读,并相应地调整应用程序中可能要更改X的部分(例如,删除旧对象,添加新对象一。

        4
  •  1
  •   CodesInChaos    15 年前

    对于简单的对象,可以使它们不可变。但不要做过头。如果使复杂对象不可变,则可能需要创建一些生成器类以使使用它成为可接受的。这通常是不值得的。

    而且不应该使具有不可变标识的对象。如果希望对象的行为类似于值类型,通常会使其不可变。

    在我的一个项目中,我通过只给基类提供getter并在派生类中隐藏setter来攻击不变性。然后为了使对象不可变,我将其转换为基类。当然,这种类型的不变性不是由运行时强制的,如果出于其他目的需要类层次结构,那么它就不起作用。

        5
  •  1
  •   gts    15 年前

    让Changing event in Thing;Pack订阅这个事件怎么样?当您更改X in Thing时,Pack在事件处理程序中执行必要的检查,如果更改不合适,则抛出异常?

        6
  •  0
  •   Oliver    15 年前

    不幸的是,没有什么东西能比 const 在C/C++中确保(编译时)值不会被更改。

    唯一接近它们的是不变性(就像你自己和Itay提到的),但是它没有像 常量

    如果在某些情况下需要可变,则只能在运行时执行以下操作

    class Thing
    {
        public bool IsReadOnly {get; set;}
    
        private int _X;
        public int X
        {
            get
            {
                return _X;
            }
            set
            {
                if(IsReadOnly)
                {
                    throw new ArgumentException("X");
                }
    
                _X = value;
            }
        }
    }
    

    但是它污染了你的代码 try/catch 以及改变 IsReadOnly . 所以不是很优雅,但也许有些事情要考虑。

        7
  •  0
  •   Tony    15 年前

    如果从集合中移除要修改的内容,而不只是允许直接访问,则在放回时将对其进行检查。

    当然,缺点是你必须记住把它放回背包里!

    或者在包上提供方法来修改内容,这样您就可以检查设置的值是否违反域规则。

    推荐文章