views:

298

answers:

7

Your job is to design a Project Plan class library which supports the tracking of tasks (similar to how MS Project works). This class library has a Task object (among others).

The Task object has a EstimatedHours (Double), StartDate (DateTime), and EndDate (DateTime) properties, among others. A Task object can have one parent Task, and several children Task objects. The EstimatedHours, StartDate, and EndDate properties of a Task which has children (is a parent) depend on those properties of its immediate children. The parent Task's StartDate is the earliest StartDate of its children. The parent Task's EndDate is the latest EndDate of its children. The parent Task's EstimatedHours is the sum of its children's EstimatedHours. Therefore, it is invalid to change these properties on a Task which has children.

How would you handle the use case where the EstimatedHours, StartDate, or EndDate are changed on a task which has a parent? (The parent's properties are a reflection of its children, so any changes to children may require the parent's properties to be adjusted to reflect the changes appropriately)

One option is to have an event for when each property changes. A parent Task would listen for these events on its immediate children Task objects, and make appropriate changes to its own properties when those events occurred. Is this a good approach, or is there a better way? How would you do it?

Here's a basic idea of what a Task object might look like:

Public Class Task

  Private mChildren As List(Of Task)

  Private mEndDate As DateTime = DateTime.MinVlue
  Public Property EndDate() As DateTime
    Get
      Return mEndDate 
    End Get
    Set(ByVal value As DateTime)
      mEndDate = value
      'What to do here?
    End Set
  End Property

  Private mEstimatedHours As Double = 0.0
  Public Property EstimatedHours() As Double 
    Get
      Return mEstimatedHours 
    End Get
    Set(ByVal value As Double)
      mEstimatedHours = value
      'What to do here?
    End Set
  End Property

  Private mStartDate As DateTime = DateTime.MinVlue
  Public Property StartDate() As DateTime
    Get
      Return mStartDate 
    End Get
    Set(ByVal value As DateTime)
      mStartDate = value
      'What to do here?
    End Set
  End Property

End Class
+4  A: 

The right approach to solve this problem will be to use Observer Design Pattern. Detailed explaination of implementing Observer pattern is beyond the scope of this discussion. But here are some great links for Observer Pattern. One link is here and another is here.

http://www.dofactory.com/Patterns/PatternObserver.aspx

http://en.wikipedia.org/wiki/Observer_pattern

Hope this helps.

Ruchit S.

this. __curious_geek
A: 

Please for the love of god do not make another project management web application.

Shawn Simon
Very helpful. I think the problem is valid, even if you would not consider the problem domain.
Dave Van den Eynde
-1 The principles can apply to many situations, not just a proj. mgmt. web app.
Brandon Montgomery
+1  A: 

I would not consider this a part of the Model's responsibility, but rather of the Controller on top of it.

Adding events or observer patterns to a model adds complexity in other areas, such as serialization, which you will want to avoid.

Make it the responsibility of the class that makes the modification, not the model itself. Remember: the model's responsibility is to contain the information, not imply the business rules.

Dave Van den Eynde
+1  A: 

Keep in mind that when one event in an event chain throws an exception, the following events are not going to be invoked. So if there are other events registered to the data it may be possible your event will not be called.

If it's crucial for your application that the base task is never out of touch with its children, then don't use events.

Michael Barth
+1  A: 

I would first build the object model so that it calculates on the fly the values. I am going to give you C# as I am most comftorble with it (I am also using fields instead of properties to keep the sample small):

public class Task
{

    public List<Task> Children=new List<Task>();
    public Task Parent;   
    private int _duration;

    public int Duration
    {

       get
       {
          if (Children.Count>0)
          { 
              return SumChildrenDuration();
          }

          return _duration;
       }

       set 
       {
          if (children.Count>0)
              throw new Exception("Can only add to leaves");
          _duration=value;
       }
    }
}

Once you have this in place you now have all the code you need to run your system. You might find that the system performs well enough and leave it like this. Otherwise you can add in additional functionality to cache the result and then reset the cache when an object changes. Whatever you do be sure to profile it closely as you want to make sure your caching and expiration isn't more expensive then just calculating on the fly.

JoshBerke
+2  A: 

I'm not sure this is how I'd actually do it, but here's a different option: Instead of allowing a Task to have children, use two objects, a Task and a TaskSet that implement an ITask interface. A Task would have its own StartDate, EndDate, and EstimatedHours, but a TaskSet would dynamically calculate those values from its child tasks. Use a service to add and remove children to an ITask. For adding, it would convert a Task into a TaskSet when the first child is added. For removing, it would convert the TaskSet back into a Task when then last child is removed and set the properties from the values on the last child.

Jamie Ide
A: 

I tell my ASP.NET developers, "Events supervise. Methods do the work."

Events should be little more than IFblocks calling methods. No try/catches etc.
Methods do all of the data access/manipulation/validation/calculation etc.
This creates "reusable-code" mindsets in my developers as well.

It keeps things separated.
It also parallels the MVC concepts pretty well.

Controllers react to events. They supervise. They call Model methods.
Models do the work.

It's not a perfect parallel.
True, it is simplistic, but it makes for pretty good guidelines.