tags:

views:

166

answers:

7

While trying to model my domain, I came across the following problem. Let's image we have a Thing:

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

Things have a property X. Then, there are Packs, which aggregate Things. But the domain requires that there is some restriction on the Things the Packs can hold. Let it be for example that the cumulative value of Xes can't be higher then some specific limit:

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

So I check before adding a Thing to a Pack for the condition, but it's still so easy to get into troubles:

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

In C++ the solution would be to make a copy upon insertion and return const reference in the indexer. How to design around this problem in C# (and probably Java)? I just can't seem to think of a good solution:

  1. make Thing immutable - but what if it needs to be mutable?
  2. watch the Things in Pack with event/observer - but that means that Pack imposes the design of Thing; what if Things have more properties? Then I'll end up with just one event due to the need for Pack to watch for changes - that seems awkward to me.

Any ideas or preferred solutions?

+5  A: 

Make Thing immutable.

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

In addition, instead of if (myThings.Sum(t => t.X) + thing.X > MaxValue) I think it is better to hold a sum field in pack so you don't have to recalculate the sum each time.

EDIT
Sorry - I missed the fact that you stated that you need it mutable.
But... How would your c++ solution work? I don't know much c++ but doesn't c++ constant reference will prevent the change on instances that on the Pack?

EDIT2
Use an interface

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; }
    }
}
Itay
In C++, if you have a list of const references to Pack, you won't be able to do pack[0].setX(X) if setX was declared const as you are modifying the value of X and this will trigger a compiler error. However, there is nothing that will prevent you from adding thingOne to the pack and then modifying thingOne directly.
Philip Goh
@Phillip - Thanks
Itay
Thanks for your reply, but I don't think it's a good solution. I think you're on the edge of overusing the interface (I don't think they were meant for what you're trying to achieve). Second thing is that I just can't go around and make copies of Things so easily, because they have some identity (they are loaded from data access layer).
Stefan
+1  A: 

For simple objects you can make them immutable. But don't overdo it. If you make complex objects immutable you probably need to create some builder class to make working with it acceptable. And that's usually not worth it.

And you shouldn't make objects which have an identity immutable. Typically you make an object immutable if you want it to behave like a value type.

In one of my projects I hacked in immutability by giving the base class only getters and hiding the setters in the derived class. Then to make the object immutable I'd cast it to the baseclass. Of course this type of immutability isn't enforced by the runtime and doesn't work if you need your class hierarchy for other purposes.

CodeInChaos
Those objects do have identity, so making them immutable is not a solution - thanks for pointing that out!
Stefan
A: 

Unfortunately there is nothing in C# that has so much power like const in C/C++ to make sure (at compile time) that a value won't be changed.

The only thing that comes them nearly is immutability (like mentioned by yourself and Itay), but it doesn't have the flexibility like const. :(

If you need it mutable in some scenarios you can only do something at runtime like this

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

but it pollutes your code with try/catch and changing the value of IsReadOnly. So not very elegant, but maybe something to consider.

Oliver
I guess there's no way to not pollute the Thing class, unfortunatelly.
Stefan
+2  A: 

Following your statements you really have some business logic behind the X which must also be applied when X is set. You should think carefully about how you design X if it is supposed to trigger some business logic. Its more than merely a property.

You might consider the following solutions:

  • Do you really need to allow to change X? Even if the class is not immutable you can still make X read-only.
  • Provide a SetX(...) method which encapsulates the business logic to check whether the Thing is part of a Pack and call that Pack's validation logic.
  • Of course instead of a setter method you can use the property set { } as well for that logic.
  • In Pack.Add, create a copy of the Thing, which is actually a subclass that adds the validation logic.
  • ...

Whatever you do, you won't get around the following very basic design question: Either you allow for changing X, then you have to introduce the validation / business logic in every part that allows for changing X. Or you make X read-only and adapt the parts of the application that might want to change X accordingly (e.g. remove old object, add new one...).

chiccodoro
"(e.g. remove old object, add new one...)"Excellent point chiccodoro, I didn't think of that in my answer. Things could be made to be read only Value objects and destroyed/created when the value needs to change. +1
Tony
Fair points, thanks for the answer. In reality X is from-to date pair and Packs can't hold overlapping things, so there's not much logic behind that. And yes, I do want to be able to modify those dates.
Stefan
Hi Stefan. Not to allow overlapping dates *is* business logic. If you need to allow for changing the dates afterwards, then the const in C++ wouldn't be of any use for you, would it? You'll need to keep a reference to the Pack in your Thing or raise an event like you already mentioned in your question, no way around that...
chiccodoro
@chiccodoro: yes, it is business logic, but stuffing that logic into Thing is wrong in my opinion. The logic "naturally belongs" to Pack. I want the dates to be modifyable and if I had a const at my disposal I could ENFORCE that if you want to modify the Thing, you need to remove it, modify it and then insert it, upon which I can check the consistency of Pack again. Simple, clean and... not gonna happen in C#.
Stefan
@Stefan: As you mentioned, you could define a "BeforeSetX" event with a CancelEvent handler to your `Thing`. In `Pack.Add` you'd then add an event handler which either cancels any try to change X (virtually making it unmodifiable until it's removed from the Pack) or do the validation and throwing an exception or cancelling the event if the validation fails.
chiccodoro
A: 

I would consider redesigning the way you use the Pack.

If you remove the Thing you wish to modify from the collection, and not just allow direct access, it would be checked when it is put back.

Of course, the down side is you having to remember to put it back in the Pack!

Alternatively provide methods on the Pack to modify Things, so you can check the value being set does not violate domain rules.

Tony
To make the first design work I guess I would have to disallow getting the reference to the Things in the specific Pack. But that would even disallow me to enumerate the Pack.
Stefan
Yes, that would prevent enumeration. How about making them read-only value objects? When you want to change the value construct a new object and destroy the old one, as suggested by chiccodoro. It will necessitate removal and reinsertion to the Pack but would allow the changed value to be checked.
Tony
Something else to consider, it may be the fact of allowing get/set of a member variable which is the cause of your design dilemma. Here's an interesting article regarding getter/setter methods (it talks about Java but the principles are applicable to any language):http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html
Tony
+2  A: 

You could have Thing implement the interface iReadOnlyThing, which only has ReadOnly acess to each of Thing's properties.

It means implementing most of your properties twice, and it also means trusting other coders to respect the use of the iReadOnlyThing interface and not sneakily cast any iReadOnlyThings back to Things.

You might go so far as implementing a read-only container class in the same way that

System.Collections.ObjectModel.ObservableCollection<T>

and

System.Collections.ObjectModel.ReadOnlyObservableCollection<T>

do. ReadOnlyObservableCollection's constructor takes an ObservableCollection as its argument and allows read access to the collection without allowing write access. It has the advantage of not allowing the ReadOnlyObservableCollection to be cast back to an ObservableCollection.

I feel I should point out that the Model-View-ViewModel pattern used in WPF development does, in fact work on the principle that you suggest, of having a single NotifyPropertyChanged event, which contains a string identifying the property that changed...

The coding overhead of MVVM pattern is significant, but some people seem to like it...

Frosty840
Thanks for suggesting NotifyPropertyChanged. Seems a reasonable solution, though I don't like the resulting design.
Stefan
I have to admit that the transition from hard-coded, logical connections to the text-based world of WPF and XAML is a distressing one. Mistyping "myThing" as "myThnig" as a data-source in the XAML, for example, produces none of the compile errors that would normally result from such an error in the "real" code; the listeners on the WPF control simply start listening to event handlers for nonexistent objects... You'd think that would look like a bug to someone, but apparently it's actually a feature...
Frosty840
+1  A: 

How about having Changing event in Thing; Pack subscribes to this event and when you change the X in Thing, Pack do the necessary checking in the event handler and if the change doesnt fit; throws exception?

Khurram Aziz
Yes, seems the best solution for the problem. But as I mentioned, makes the interface of the Thing a little awkward.
Stefan