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 OrderThe problem here, is that I want to have 'controlled access' to the
{
private List<OrderLine> orderLines
= new List<OrderLine>();
}
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 )A consumer of the code should always use the
{
ol.OwningOrder = this;
this.orderLines.Add (ol);
}
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 OrderUnnecessary to say that this is just a boring task.
{
private List<OrderLine> orderLines = ...
public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
orderLines.Add (ol);
}
public int NumberOfOrderLines
{
get
{
return orderLines.Count;
}
}
}
Then, to be able to iterate through the
OrderLines
of an Order
, you could write code like this:public class OrderActually, I think this is plain ugly.
{
private List<OrderLine> orderLines = ...
...
public OrderLine[] GetOrderLines()
{
return orderLines.ToArray();
}
}
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 OrderThis is the C# 2.0 version.
{
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();
}
}
}
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 OrderHere, 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
{
private IList orderLines = new ArrayList();
public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}
public IList OrderLines
{
get
{
return ArrayList.ReadOnly (orderLines);
}
}
}
OrderLine
to the Order
in an uncontrolled fashion like this:Order o = new Order();In this case, a
OrderLine ol = new OrderLine();
o.OrderLines.Add (ol);
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:
Why can't you just make a method for returning an Iterator of your collection?
public Iterator getIterator() {
return _orderlines.getIterator();
}
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...
And gone is your abstraction!
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 ?
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?
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).
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.
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.
// Sorry for the late comment, haven't seen my RSS readed for two weeks
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 :) )
>> 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.
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.
>> 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;
}
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.
>> 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.
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
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.
Een reactie posten