I have two client types, an “Observer“-type and a “Subject“-type. They’re both associated with a hierarchy of groups.
The Observer will receive (calendar) data from the groups it is associated with throughout the different hierarchies. This data is calculated by combining data from ‘parent’ groups of the group trying to collect data (each group can have only one parent).
The Subject will be able to create the data (that the Observers will receive) in the groups they’re associated with.
When data is created in a group, all ‘children’ of the group will have the data as well, and they will be able to make their own version of a specific area of the data, but still linked to the original data created (in my specific implementation, the original data will contain time-period(s) and headline, while the subgroups specify the rest of the data for the receivers directly linked to their respective groups).
However, when the Subject creates data, it has to check if all affected Observers have any data that conflicts with this, which means a huge recursive function, as far as I can understand.
So I think this can be summed up to the fact that I need to be able to have a hierarchy that you can go up and down in, and some places be able to treat them as a whole (recursion, basically).
Also, I’m not just aiming at a solution that works. I’m hoping to find a solution that is relatively easy to understand (architecture-wise at least) and also flexible enough to be able to easily receive additional functionality in the future.
Is there a design pattern, or a good practice to go by, to solve this problem or similar hierarchy problems?
EDIT:
Here’s the design I have:
The “Phoenix”-class is named that way because I didn’t think of an appropriate name yet.
But besides this I need to be able to hide specific activities for specific observers, even though they are attached to them through the groups.
A little Off-topic:
Personally, I feel that I should be able to chop this problem down to smaller problems, but it escapes me how. I think it’s because it involves multiple recursive functionalities that aren’t associated with each other and different client types that needs to get information in different ways. I can’t really wrap my head around it. If anyone can guide me in a direction of how to become better at encapsulating hierarchy problems, I’d be very glad to receive that as well.
9
Here’s a simple “Group” implementation that allows you to navigate to the Root, and navigate that Root’s tree as a collection.
public class Group
{
public Group Parent
public List<Group> Children
public IEnumerable<Group> Parents()
{
Group result = this;
while (result.Parent != null)
{
result = result.Parent;
yield return result;
}
}
public Group Root()
{
return Parents.LastOrDefault() ?? this;
}
public IEnumerable<Group> WalkTreeBreadthFirst(
{
//http://en.wikipedia.org/wiki/Breadth-first_search
HashSet<Group> seenIt = new HashSet<Group>()
Queue<Group> toVisit = new Queue<Group>();
toVisit.Enqueue(this);
while (toVisit.Any())
{
Group item = toVisit.Dequeue();
if (!seenIt.Contains(item))
{
seenIt.Add(item);
foreach (Group child in item.Children)
{
toVisit.Enqueue(child);
}
yield return item;
}
}
}
public static IEnumerable<Group> WalkTreeDepthFirst()
{
// http://en.wikipedia.org/wiki/Depth-first_search
HashSet<Group> seenIt = new HashSet<Group>();
Stack<Group> toVisit = new Stack<Group>();
toVisit.Push(this);
while (toVisit.Any())
{
Group item = toVisit.Pop();
if (!seenIt.Contains(item))
{
seenIt.Add(item);
foreach (Group child in item.Children.Reverse())
{
toVisit.Push(child);
}
yield return item;
}
}
}
}
So – given a group, you can walk that Group’s tree:
Group myGroup = GetGroup();
Group root = myGroup.Root;
foreach(Group inTree in root.WalkTreeBreadthFirst())
{
//do something with inTree Group.
}
My hope in posting this, is that by showing how to navigate a tree (and dispelling the complexity thereof), you may be able to visualize about the operations you want to perform on the tree, and then revisit the patterns on your own to see what best applies.
With the limited view we have of the usage or implementation requirements of your system it’s hard to get too specific. For example, things that would come into consideration might be:
- is the system highly concurrent (lots of users)?
- what is the read/write ratio of data access? (high read, low write is common)
As for patterns etc., I would worry less about what exact patterns crop up in your solution, and more about the design of the actual solution. I think knowledge of design patterns is useful, but not the be-all and end-all: to use a writer analogy, design patterns are more like a dictionary of commonly seen phrases, rather than a dictionary of sentences you must write an entire book from.
Your diagram looks generally ok to me.
There’s one mechanism you’ve not mentioned and that is of having some sort of cache in your hierarchy. Obviously you must implement this with great care, but it could significantly improve the performance of your system. Here’s a simple take on it (caveat emptor):
For each node in your hierarchy, store inherited data with the node. Do this lazily or pro-actively, that’s up to you. When an update is made the hierarchy, you can either regenerate the cache data for all affected nodes there and then, or set ‘dirty’ flags in the appropriate places, and have the affected data lazily re-generated when necessary.
I have no idea how appropriate this is in your system, but may be worth considering.
Also, this question on S.O. may be relevant:
https://stackoverflow.com/questions/1567935/how-to-do-inheritance-modeling-in-relational-databases
I know this is Kind of Obvious but I am going to say it anyway,
I think you should take a look at the Observer Pattern
you mentioned that you have an Observer Type and what you have looks kind of like the Observer Pattern to me.
couple of links:
DoFactory
oodesign
check those out.
otherwise I would just Code what you have in your Diagram and then use the Design Pattern to simplify if necessary. you already know what needs to happen and how the program is supposed to work. Write some Code and see if it still fits.