Here's a common scenario that's always frustrating for me to deal with.
I have an object model with a parent object. The parent contains some child objects. Something like this.
public class Zoo
{
public List<Animal> Animals { get; set; }
public bool IsDirty { get; set; }
}
Each child object has various data and methods
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public void MakeMess()
{
...
}
}
When the child changes, in this case when the MakeMess method is called, some value in the parent needs to be updated. Let's say when a certain threshold of Animal's have made a mess, then the Zoo's IsDirty flag needs to be set.
There are a few ways to handle this scenario (that I know of).
1) Each Animal can have a parent Zoo reference in order to communicate changes.
public class Animal
{
public Zoo Parent { get; set; }
...
public void MakeMess()
{
Parent.OnAnimalMadeMess();
}
}
This feels like the worst option since it couples Animal to its parent object. What if I want an animal who lives in a house?
2) Another option, if you're using a language that supports events (like C#) is to have the parent subscribe to change events.
public class Animal
{
public event OnMakeMessDelegate OnMakeMess;
public void MakeMess()
{
OnMakeMess();
}
}
public class Zoo
{
...
public void SubscribeToChanges()
{
foreach (var animal in Animals)
{
animal.OnMakeMess += new OnMakeMessDelegate(OnMakeMessHandler);
}
}
public void OnMakeMessHandler(object sender, EventArgs e)
{
...
}
}
This seems to work but from experience gets hard to maintain. If Animals ever change Zoo's you have to unsubscribe events at the old Zoo and resubscribe at the new Zoo. This only gets worse as the composition tree gets deeper.
3) The other option is to move the logic up to the parent.
public class Zoo
{
public void AnimalMakesMess(Animal animal)
{
...
}
}
This seems very unnatural and causes duplication of logic. For example, if I had a House object that doesn't share any common inheritance parent with Zoo..
public class House
{
// Now I have to duplicate this logic
public void AnimalMakesMess(Animal animal)
{
...
}
}
I have not yet found a good strategy for dealing with these situations. What else is available? How can this be made simpler?
Best Answer
I had to deal with this a couple times. The first time I used option 2 (events) and as you said it became really complicated. If you go that route, I highly suggest you need very thorough unit tests to make sure the events are done correctly and you're not leaving dangling references, otherwise it's a really big pain to debug.
The second time, I just implemented the parent property as a function of the children, so keep a
Dirty
property on each animal, and letAnimal.IsDirty
returnthis.Animals.Any(x => x.IsDirty)
. That was in the model. Above the model there was a Controller, and the controller's job was to know that after I changed the model (all actions on the model were passed through the controller so it knew that something had changed), then it knew it had to call certain re-evaluation functions, like triggering theZooMaintenance
department to check if theZoo
was dirty again. Alternatively I could just push theZooMaintenance
checks off until some scheduled later time (every 100 ms, 1 second, 2 minutes, 24 hours, whatever was necessary).I found the latter has been much simpler to maintain, and my fears of performance problems never materialized.
Edit
Another way of dealing with this is a Message Bus pattern. Rather than using a
Controller
like in my example, you inject every object with anIMessageBus
service. TheAnimal
class can then publish a message, like "Mess Made" and yourZoo
class can subscribe to the "Mess Made" message. The message bus service will take care of notifying theZoo
when any animal publishes one of those messages, and it can re-evaluate itsIsDirty
property.This has the advantage that
Animals
no longer need aZoo
reference, and theZoo
doesn't need to worry about subscribing and unsubscribing from events from every singleAnimal
. The penalty is that allZoo
classes subscribing to that message will have to re-evaluate their properties, even if it wasn't one of its animals. That may or may not be a big deal. If there's only one or twoZoo
instances, it's probably fine.Edit 2
Don't discount the simplicity of option 1. Anyone revisiting the code won't have much problem understanding it. It'll be obvious to someone looking at the
Animal
class that whenMakeMess
is called that it propagates the message up to theZoo
and it'll be obvious to theZoo
class where it gets its messages from. Remember that in object-oriented programming, a method call used to be called a "message". In fact, the only time it makes much sense to break from option 1 is if more than just theZoo
has to be notified if theAnimal
makes a mess. If there were more objects that needed to be notified, then I would probably move to a message bus or a controller.