I have seen - and written - a lot of code that does this:
But doesn't this break the three rules? First we are exposing the objects internal structure (the fact that myObject has a collection of Objects inside it). Secondly we break Demeter in three ways by:
foreach(Object obj in myObject.Objects)
{
obj.DoSomething();
}
- invoking the GetEnumerator (in C#) method of IEnumerable which was returned by myObject (via Objects)
- invoking the MoveNext (which incidently breaks Command-Query seperation) and Current methods of IEnumerator (in C#) which was returned by IEnumerable which was returned by myObject
- invoking the DoSomething() method of obj which was returned by the Current method of IEnumerator which was returned by IEnumerable which was returned by myObject (see why it's called a train wreck?)
Looping arrays and collections is very common and in most programs exposing the collection is the norm. But what happens when you need to change the way the collection works? What if you decide to replace your objects array with a HashTable or a Set? Thankfully most OOP languages gives you some sort of iterator so, in C# for example, you can just expose IEnumerator and you might be alright. But this still breaks the three rules and what do you do if your underlying data structure doesn't have an IEnumerable? What if you're dealing with some legacy COM+ object and you have revert back to the old for or while? Or even worse, the data structure doesn't even seem to have a simple structure (not so uncommon)? Just exposing an enumerator isn't so easy but we still work around it by doing something like this:
And then in the client we have:
IEnumerator Objects
{
IList list;
for(int i = 0; i < comObject.Count; i++)
{
list.Add(comObject[i]);
}
return list.GetEnumerator();
}
Hang on a second: we loop so we can loop? Doesn't that sound a bit strange? To make matters worse we seem to do this as well:
foreach(Object obj in myObject.Objects)
{
// here we go again!
}
How many times do you want to write that damn loop? So what's the alternative? To implement the three rules: hide the collection and stop exposing it and then tell the myObject to loop it for you. Quite simply:
void SaveEverything()
{
foreach(Object obj in myObject.Objects)
{
database.Save(obj);
}
}
... Somewhere else in the program ...
void DisplayEverything()
{
foreach(Object obj in myObject.Objects)
{
ui.Display(obj);
}
}
... Again somewhere else ...
void SendEmail()
{
foreach(Object obj in myObject.Objects)
{
email.Send(obj);
}
}
All you need to do is pass your receiver to MyObject and it will do all the work for you:
public class MyObject
{
void Loop(IReceiver receiver)
{
foreach(Object obj in objects)
{
receiver.Recieve(obj);
}
}
}
Or if you prefer you can use delegates and anonoymous methods:
myObject.Loop(databaseSaver);
public class DatabaseSaver : IReceiver
{
void Receive(Object obj)
{
database.Save(obj);
}
}
myObject().Loop(delegate(Object obj) { database.Save(obj); });
public delegate void LoopDelegate(Object obj);
public class MyObject
{
public void Loop(LoopDelegate loopDelegate)
{
foreach(Object obj in objects)
{
loopDelegate(obj);
}
}
}
Now if you need to change the objects internal data structure of the way you iterate it (for performance reasons etc.) you can do so with ease and without breaking any of your clients.
Although with simple loops it all looks straightforward enough, it is with more complex structures that you gain a real advantage. In a parent child structure for example you may find a loop such as this:
Writing this loop all over the place can be fairly cumbersome, and what if you don't want to expose the structure in this way? By using the pattern above you have more control over the semantics of the object. For example you could present the list flat and merle passes each object across as above (and thus loosing nested loops all over your code) or you could expose some structure:
foreach(Parent parent in foo.Parents)
{
foreach(Child child in foo.Children)
{
// do something
}
}
public interface ParentReceiver
{
ChildReceiver CreateChildReceiver();
void ReceiveParent(Parent, ChildReceiver);
}
public interface ChildReceiver
{
void ReceiveParent(ParentReceiver);
}
public class Parents
{
public void Loop(ParentReceiver receiver)
{
foreach(Parent parent in parents)
{
ChildReceiver childReceiver = receiver.CreateChildReceiver();
parent.Loop(childReceiver);
receiver.ReceiveParent(parent, childReceiver);
}
}
}
public class Parent
{
public void Loop(ChildReceiver receiver)
{
for(int i = 0; i < children.count; i++)
{
// don't pass any illegitimate children!
if(children[i].IsNotIllegitimate)
{
receiver.Receive(children[i]);
}
}
}
}
As you can see by following the three rules and telling the object what to do, and only the immediate object what to do, we can not only express the structure of the objects better and force clients to work with that structure, we also loosen the object couplings.
Tip:
For those pre .NET 2.0 people out there, or those who use languages that don't have generics, by using the above pattern you can strongly type your objects even if you can't strongly type the internal collections.
No comments:
Post a Comment