zondag 3 september 2006

Collections in Business Entities

A while ago, I've been thinking on what would be the best approach to work with collections inside business entities.

As an example, I'll refer back to the domain classes I'm using in another post of me.
There, I have a class Order, and an OrderLine class. The Order class contains a collection of OrderLines.
In C# 2.0, this could look like this:

public class Order
{
private List<OrderLine> orderLines
= new List<OrderLine>();
}
The problem here, is that I want to have 'controlled access' to the OrderLines collection in the Order class.
What I mean is, that a consumer of these classes should not be able to add an OrderLine to the Order directly, since some additional action(s) need to be executed.



More precisely, if an OrderLine is added to the Order, I want to set a member of the OrderLine, so that the OrderLine knows to which Order it belongs.
Therefore, I create an AddOrderLine method in the Order class:
public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
this.orderLines.Add (ol);
}
A consumer of the code should always use the AddOrderLine method if he wants to add an OrderLine to the Order.
You can enforce this easily by not making the orderLines member public; easy enough.
However, at one time, the consumer of your code (or you :) ) will want to iterate through the OrderLines of the Order, or he will want to know how many OrderLines an Order contains.



Now, you'll have several options to achieve this:

One solution is to expose the orderLines collection to the public. In other words: create a public property which exposes the collection to the outside:

public class Order
{
private List<OrderLine> orderLines
= new List<OrderLine>();

public List<OrderLine> OrderLines
{
get
{
return orderLines;
}
}
}

Although this is a solution, I do not like it.
Now, we're unable to force the user to use the AddOrderLine method. Since the OrderLines collection is now publicly exposed, it is now possible to just use the Add method of the List to add OrderLines to the Order.
The documentation of the classes could of course mention that you should always use the AddOrderLine method, but there's no real hard constraint here. (If the classes are used inappropriatly, the program could of course crash, so then there is a constraint after all. ;) However, then the programmer using those classes will maybe lose a lot of time to detect the error he made.

Another solution is to keep the OrderLines collection private, and to add some extra members to the Order class. In this way, uncontrolled access to the OrderLines is not possible.
However, there are offcourse disadvantages to this approach as well. First of all, you'll have to write some tedious code that just delegates the functionality to the Collection class, like this:

public class Order
{
private List<OrderLine> orderLines = ...

public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
orderLines.Add (ol);
}

public int NumberOfOrderLines
{
get
{
return orderLines.Count;
}
}
}
Unnecessary to say that this is just a boring task.
Then, to be able to iterate through the OrderLines of an Order, you could write code like this:
public class Order
{
private List<OrderLine> orderLines = ...

...

public OrderLine[] GetOrderLines()
{
return orderLines.ToArray();
}
}
Actually, I think this is plain ugly.

I always have to make a choice between these 2 approaches, where each one has his disadvantages. After doing this too much, I wanted a better solution for this problem, and after some reading and experimenting, I finally found one. It is in fact such a simple solution that I can't imagine why I haven't been using this one much earlier...

It's just nothing more then this:
Keep the collection private, create the necessary methods to provide the necessary controlled access to the collection (for instance the AddOrderLine method, and create a public property which returns a read-only instance of the collection, so that you can iterate through the collection, change existing instances of objects within the collection (at least, this is only true if you have reference types in the collection; you will not be allowed to modify value types on a ReadOnlyCollection, get the number of objects that are in the collection, ...

In code, it looks like this:

class Order
{
private List<OrderLine> orderLines =
new List<OrderLine>();

public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}

public ReadOnlyCollection<OrderLine> OrderLines
{
get
{
return orderLines.AsReadOnly();
}
}

}
This is the C# 2.0 version.
Now, it is impossible to add OrderLines in an uncontrolled way, but it is possible to iterate the OrderLines that are in the collection, and modify existing OrderLine objects that are already in the collection (since it are instances of a reference type; if OrderLine was a struct, it would not be possible to modify them.

In C# 1.x, it looks like this:
class Order
{
private IList orderLines = new ArrayList();

public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}

public IList OrderLines
{
get
{
return ArrayList.ReadOnly (orderLines);
}
}
}
Here, I return a readonly copy of the ArrayList. It is still possible to iterate and change the items that are in the collection, but it is impossible to Add an OrderLine to the Order in an uncontrolled fashion like this:
Order o = new Order();
OrderLine ol = new OrderLine();
o.OrderLines.Add (ol);

In this case, a System.NotSupportedException will be thrown.
It would of course be nicer to not have an Add and Remove method on the read-only ArrayList property, like we've achieved in the C# 2.0 version (the ReadOnlyCollection class does not have Add and Remove methods).
In .NET 1.x we can achieve that by letting the property return an ICollection instead of an IList, however, then we're not able to use an indexer to retrieve an OrderLine like this:
OrderLine ol = theOrder.OrderLines[i] as OrderLine;
Therefore, I prefer to return an IList instead of an ICollection

17 opmerkingen:

Dimitri zei

Why can't you just make a method for returning an Iterator of your collection?

public Iterator getIterator() {
return _orderlines.getIterator();
}

Frederik Gheysels zei

Why returning an iterator, if I can return a read-only collection ?
I can also iterate over that read-only collection, and I get all the other advantages:
- easy checking how many items there are in the collection
- checking if a specific object is contained in the collection (Contains method)
etc...

Anoniem zei

And gone is your abstraction!

Frederik Gheysels zei

You mean the abstraction of the collection-type ?
Indeed, I'm not talking to a collection-interface anymore, but is this a bad thing ?

Indeed, if you use NHibernate, you'll have to talk to an abstract collection -type (like IList) instead of against a specific collection. But, how would you solve it ?

Mike zei

I just went thru this as well. I use Wintellect's PowerCollections that provides a ReadOnly algorithm for returning an IList<T>. I blogged about it at http://geekswithblogs.net/opiesblog/archive/2006/09/01/90046.aspx

NHibernate (the new alpha that supports generics) has issues with casting an ReadOnlyCollection to an IList<T> so I am consequently mapping to my field. You dealt with this yet?

Frederik Gheysels zei

Hi Mike,
Nice blog you have there.

I haven't really tackled this problem yet (generic collections / readonly collections and NHibernate).
I'm still using an NHibernate version that does not support generics, however I plan to look at it.

However, I'm rather thinking of letting NHibernate access the 'field' that represents the collection directly instead of using the property.
(But, I need to test this offcourse).

Frederik Gheysels zei

Letting NHibernate access the field, and specify a naming-convention works very nice.

I can use the property-name in my HQL, but NHibernate is using the field directly to set the value.
This also makes sure that, when a property contains some extra code (interception code), this gets not executed.

p.j. van de sande zei

Why don't you create a custom collection that got the responsibility for the logic. So the collection got's an Owner. And whenever a OrderLine is added to the collection, it's Owner will be set.

p.j. van de sande zei

// Sorry for the late comment, haven't seen my RSS readed for two weeks

Frederik Gheysels zei

It's an interesting thought, but this requires offcourse some additional programming (creating a number of custom collection classes).
When using NHibernate as an O/R mapper, I think NHibernate will not be able to persist this collection as well. (But I'm not sure about this, and should check it out before I say such things offcourse :) )

pjvandesande zei

>> It's an interesting thought, but
>> this requires offcourse some
>> additional programming

With code snippets and generation tools this is no problem. Normally it doesn't take me more then a minute to create the class. So time is not the issue here.

>> When using NHibernate as an
>> O/R mapper, I think NHibernate
>> will not be able to persist
>> this collection as well.

I'm not a NHibernate specialist, but you can implement the IList interface, and imho every mapper should understand that. And some mappers deliver an extra interface that you can implement for strong typed collections.

Frederik Gheysels zei

Well, then I guess it's still easier to not do it with a Collection that is responsible for determining who the owner is.

You can indeed create a custom collection that implements IList, but why bothering if the .NET framework already contains a collection that is good enough ?

What if your child-item needs to have access to it's owner ? How would you do that in that case ?
IMHO, you can't.

P.J. van de Sande zei

>> You can indeed create a custom
>> collection that implements
>> IList, but why bothering if
>> the .NET framework already
>> contains a collection that is
>> good enough ?

Responsibility and reuse? Why moving the responsibility to one class if you probally need this responsibility in more places?

>> What if your child-item needs
>> to have access to it's owner?
>> How would you do that in that case ?
>> IMHO, you can't.

This could be done by the 'Repository' in the OrderLine's Owner propertie, this could only be done if things are registered in the Repository, even if it's not commited.

public Order Owner
{
get
{
return _owner ??
(
_owner = OrderRepository.FindByOrderLine( this );
);
}
}

This could also be done in the easy way, in the OrderLineCollection class in the Add method:

public OrderLineCollection( Order owningOrder )
{
_owningOrder = owningOrder;
}

public void Add( OrderLine orderLine )
{
orderLine.OwningOrder = _owningOrder;
}

Frederik Gheysels zei

The responsability belongs to the Order class. I do not see otherwise.

Using the AddOrderLine method of the Order class will make sure that the Order property of the OrderLine is set. This is just one simple line of code, and creating a complete class just for this, seems like overkill to me.
In fact, it is a common way of working to do it like this.

>This could be done by the 'Repository' in the OrderLine's Owner propertie, this could only be done if things are registered in the Repository, even if it's not commited.

Then, for such a simple issue, you would use DB access, which is expensive.

>This could also be done in the easy way, in the OrderLineCollection class in the Add method:
What's the difference then with the way of working I use in the sample, except that you have another class which you *could* reuse ? However, who says that you will have to reuse that class ? You should always choose the solution that is most simple.

pjvandesande zei

>> Using the AddOrderLine method of
>> the Order class will make sure
>> that the Order property of the
>> OrderLine is set. This is just
>> one simple line of code, and
>> creating a complete class just
>> for this, seems like overkill
>> to me.

With the AddOrderLine, you can't use the Order Collection the way you use every collection. You can't directly add OrderLines to the collection. So, you can't bind them easy to UI controls for example.

>> In fact, it is a common way of
>> working to do it like this.

Then I disagree with the common way ;)

>> This could be done by the
>> 'Repository' in the OrderLine's
>> Owner propertie.
>> Then, for such a simple issue,
>> you would use DB access, which
>> is expensive.

No, why? You need the Order anyway, in you approuch and mine. I don't see extra calls to the database because the Repository can get it from the cache.

>> What's the difference then with
>> the way of working I use in the
>> sample, except that you have
>> another class which you *could*
>> reuse ? However, who says that
>> you will have to reuse that
>> class ? You should always
>> choose the solution that is
>> most simple.

You can reuse it, you have the responsebility on the right place (imho), you can bind it your UI very easy, you can work with collections like you allways do.

What if the order class has, OrderLines, Shipping Addresses, Tickets etc.. then you'll have a Order class with multiple Add and Remove methods. This will make the Order class complex and have wrong responsibilities imho.

Mike zei

I'm late returning to the party...:)
Thanks for the tip on the naming. Works great!
BTW...your Domain-Driven stuff is awesome...I hope to see more.

MIKE

Frederik Gheysels zei

you're welcome. :)

I'm planning to write a sequal to my DDD articles. In this article I'll talk about persisting the domain objects using NHibernate.