Mark Needham’s “Thoughts On Software Development” is a great collection of blog posts. Last year Mark wrote “Coding: The primitive obsession”, and challenged the idea that the primitive obsession anti-pattern is just about overusing low level data types like string and int.
The most frequent offender seems to be the creation of collections of things which we then query to find specific items elsewhere in the code. More often that not we'll also perform some custom logic on that particular item.
At its most extreme we might extract each of the items in a collection and make use of them separately. This somewhat defeats the purpose of the collection.
When this happens it often seems to me that what we actually have is a series of attributes of an object rather than a collection.
Mark goes on to describe how wrapping a collection can often create a more useful abstraction, then points to his debate in Wrapping collections: Inheritance vs Composition. It’s a good read.
I’ve been wrapping collections a bit myself lately, and it’s not too hard to create a custom collection that is both feature rich and descriptive of the problem it solves using composition. For example, let’s say you need to keep a collection of Stamp objects and support INotifyCollectionChanged.
public class StampCollection : IEnumerable<Stamp>, INotifyCollectionChanged { public StampCollection() { _items = new ObservableCollection<Stamp>(); _items.CollectionChanged += ItemsCollectionChanged; } public void AddStamp(Stamp newStamp) { /* logic */ _items.Add(newStamp); } public void RemoveStamp(Stamp stamp) { /* logic */ _items.Remove(stamp); } public IEnumerator<Stamp> GetEnumerator() { return _items.OrderBy(stamp => stamp.Something) .GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { }; void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { CollectionChanged(this, e); } readonly ObservableCollection<Stamp> _items; }
Yes, it’s a bit of code, but it:
Comments
The "collection" is really just an API pattern designed to provide read/write access to a set of items. Using component.AddItem and component.RemoveItem methods is equivalent to using component.Items.Add() and component.Items.Remove(); the only thing that changes is the API style.
The reason why the latter is better is because it standardizes list access, and lets the framework do neat things like collection initializers or LINQ. When you hide away that interface, you're taking away a lot of useful behavior that users of your "collection" would expect. Calling something a collection when it isn't one is just going to be confusing for everyone.
IEnumerable - a sequentially readable set of items.
ICollection - a writable set of items with some sort of positional accessor, normally used for APIs.
IList - a writable set of items with manipulation methods (sort, etc.)
All of the classes in the framework that call themselves a "collection" implement ICollection.
A "collection" that only implements IEnumerable isn't really a collection but just an enumerable. You can't, for example, access an enumerable by position.
Sometimes it makes sense to restrict that, but if you're going to restrict your class to enumeration access only, then it doesn't make sense to have add/remove methods either. If you are going to have add/remove methods, it might as well be an ICollection.
I agree with Ilia but pay attention to the fact that Microsoft has change the ICollection interface in .NET 2 with ICollection<T> which not (than) has Add and Remove methods.
Also remember that ObservableCollection<T> (which is great class) is in a WPF assembly. So we can use this as base class or internal member but it's should be in System.Core or something like that.
Not just UI application need to know when collection has changed.
Ido.