tags:

views:

205

answers:

4

Here is the class structure I'd like to have:

  public class AllocationNEW<TActivity, TResource> 
    where TActivity : AllocatableActivity<TActivity>
    where TResource : AllocatableResource<TResource>
  {

   public TResource Resource { get; private set; }
   public TActivity Activity { get; private set; }
   public TimeQuantity Period { get; private set; }

   public AllocationNEW(TResource resource, TActivity activity, DateTime eventDate, TimeQuantity timeSpent)
    {
        Check.RequireNotNull<IResource>(resource);
        Resource = resource;

        Check.RequireNotNull<Activities.Activity>(activity);
        Activity = activity;

        Check.Require(timeSpent.Amount >= 0, Msgs.Allocation_NegativeTimeSpent);
        TimeSpentPeriod = _toTimeSpentPeriod(eventDate, timeSpent);
    }
   }


public class AllocatableResource<TResource>
{
    public string Description { get; protected set; }
    public string BusinessId { get; protected set; }     
}

public class AllocatableActivity<TActivity>
{

    public string Description { get; protected set; }
    public string BusinessId { get; protected set; }

    protected readonly IDictionary<DateTime, Allocation<TActivity, TResource>> _allocations;

    public virtual void ClockIn<TResource>(DateTime eventDate, TResource resource, TimeQuantity timeSpent)
    {
        var entry = new Allocation<TActivity, TResource>(resource, this, eventDate, timeSpent);
        if (_allocations.ContainsKey(eventDate))
        {
            Check.Require(_allocations[eventDate].Resource.Equals(resource),
                          "This method requires that the same resource is the resource in this allocation.");
            _allocations[eventDate] = entry;
        }
        else _allocations.Add(eventDate, entry);
    }
}

AllocatableActivity is where this scheme breaks down, of course. I need a collection of Allocations to work with in this class, but I don't know what TResource is going to be until a client uses the ClockIn method.

I've been stuck on various ideas on how to solve the Allocation class with Resource and Activity types for awhile, so I'm hoping that I'm missing something obvious. It seems like it should be solvable with Generics.

Cheers

MORE INFO

Here's some examples of activities:

public class ProjectActivity : AllocatableActivity<Project>
{
    public ProjectActivity(Project project) {
        _project = project;
        Description = _project.Description;
        BusinessId = _project.Code.ToString();
    }
    // private readonly Project _project; --> shouldn't need anymore
}

public class AccountingActivity : AllocatableActivity<Account>
{

    public AccountingActivity(Account account)
    {
        _account = account;
        Description = account.Description;
        BusinessId = account.AccountId;
    }
    // private readonly Account _account;
}

Here's an example of a resource:

public class StaffMemberResource : AllocatableResource<StaffMember>
{
    public StaffMemberResource(StaffMember staffMember) {
        Description = staffMember.Name.ToString();
        BusinessId = staffMember.Number;
    }
}

Data entry would be by a single Resource. I don't know about the recursion issue yet, but otherwise there is no business reason why TResource can't be known in a data entry session other than the cost of creating the activities (there's about 300 combinations of underlying projects, accounts that I would want to treat as an AllocatableActivity at this time.

Reporting would involve multiple resources (ie, a Manager needing to sign off on time spent needs to see all time spent by her assigned resources, ad hoc reports by project, etc.)

I was laying this out without generics (interfaces and base classes) but there was some awkwardness with involving the typing, so I wanted to see if generics would make for a simpler interface.

+2  A: 

Why isn't AllocatableActivity<TActivity> also generic in TResource? That would make it all work in terms of the language - is there a business problem with that? Could you give examples of TActivity and TResource, and why you might not know what kind of resource you need when you create the activity?

Jon Skeet
@Jon - from the looks of things, I gather that ClockIn can be called with multiple types of TResource.
Adam Robinson
In that case the dictionary can't be generic.
Jon Skeet
I would love the flexibility for ClockIn to be called my multiple Resources but there is no hard requirement that says it needs to be right now. Is there some way I could use Dictionary<DateTime, object> if that became a requirement?
Berryl
Is there a base resource class that you could use instead? Basically it only makes sense to use generics if it provides type safety - and if you're going to have multiple kinds of values, there's not a lot to be gained there.
Jon Skeet
And keep the Activity class generic?
Berryl
Resource types right now are FullTimeStaffMember, HourlyStaffMember, AdminStaffMember and OutsideConsultant.
Berryl
Okay - if any activity might need more than one type of resource, then make a base class/interface if that could do anything useful, and maybe keep AllocatableActivity<TActivity> generic. It really depends though - what is it buying you? Where is the type safety useful here?
Jon Skeet
After making Resource base, it turns out that the generics weren't buying me anything in the domain or presentation I needed. I won't be positive until I finish mapping this through to the db though. Thanks for you help!
Berryl
A: 

While it's not completely clear what you're trying to accomplish, this is the general sense of what I'm seeing...

You have existing (or new) business objects that will be used for TActivity and TResource. Your AllocatableActivity class represents a single TActivity with multiple instances of a TResource (which may be different types).

On the other side of things, you have an AllocationNEW class that represents an AllocatableActivity and a single AllocatableResource (sort of the other side of the coin, as it were). This is a single activity-resource correlation class.

What you have is fairly similar to having a many-many relationship in a relational database. You have activities, you have resources, and you have an activity_resource table that allows you to link them.

I don't see a single solution that supports ALL of this without the use of reflection (and I don't see what that would get you in this scenario). What you likely need is to have a non-generic abstract base class for AllocationNEW that exposes a property for the resource as a common base type (even if the closest thing is object), then have your generic implementation inherit and expose its own strongly-typed property (or, for a "cleaner" approach, have it replace the parent weakly-typed property with your strongly-typed version). In your AllocatableActivity class, your collection should be an IDictionary<DateTime, AllocationNEWBase>, since you can't know the specific type of the TResource (and wouldn't want to limit it to one in particular, if my earlier assumption was correct). In ClockIn you can instantiate your strongly-typed generic version using the TResource type parameter.

Adam Robinson
The declaration is allowed. Its translation does not imply recursion; it merely requires that class `TResource` is derived from class `AllocatableResource<TResource>`. Such a class is trivially declared as: `class MyResource: AllocatableResource<MyResource>`.
Pavel Minaev
Thanks for that Paul, this had me nervous. I thought I'd need to read more of Jon's book before dinner rather than try and solve this :-)
Berryl
@Adam - it certainly does feel like relational db many-many, and how to persist / query this is on my mind (I'd like to use Fluent NHib but not sure if I can or how, but at least NHib).
Berryl
@Pavel: Thanks for that. Don't know why I assumed that it wouldn't work; my bad! I've edited my answer to reflect your catch.
Adam Robinson
+1  A: 

For the sake of having a unified storage in AllocatableActivity, I would have an abstract base class for Allocation and all resource types, like this:

public abstract class Resource
{
    public string Description { get; }
}

public class AllocatableResource<TResource> where TResource : Resource
{
    ...
}

public abstract class Allocation<TActivity>
{
    protected Allocation(
        Resource resource, TActivity activity, TimeQuantity period)
    {
        this.Resource = resource;
        this.Activity = activity;
        this.Period = period;
    }

    public virtual Resource Resource { get; protected set; }
    public TActivity Activity { get; protected set; }
    public TimeQuantity Period { get; protected set; }
}

public class Allocation<TActivity, TResource> : Allocation<TActivity>
    where TActivity : AllocatableActivity<TActivity>
    where TResource : AllocatableResource<TResource>
{
    public new TResource Resource { get; private set; }

    public Allocation(
        TResource resource, TActivity activity, DateTime eventDate,
        TimeQuantity timeSpent)
        : base(resource, activity, timeSpent)
    {
        ...
    }
}

Now, in AllocatableActivity, you can store all Allocations in a polymorphic fashion, like this:

public class AllocatableActivity<TActivity>
{
    protected readonly IDictionary<DateTime, Allocation<TActivity>> _allocations;

    public virtual void ClockIn<TResource>(
        DateTime eventDate, TResource resource, TimeQuantity timeSpent) 
        where TResource : Resource
    {
        var entry = new Allocation<TActivity, TResource>(
            resource, this, eventDate, timeSpent);
        if (_allocations.ContainsKey(eventDate))
        {
            Check.Require(_allocations[eventDate].Resource.Equals(resource),
                          "This method requires that the same resource is the resource in this allocation.");
            _allocations[eventDate] = entry;
        }
        else _allocations.Add(eventDate, entry);
    }
}

The reason why I added a base Resource class is that you'll undoubtedly need to list resources in your application somewhere, so there should be some sort of commonality.

Jacob
+1  A: 

You have where TActivity : AllocatableActivity<TActivity> in the definition of Allocation, but ProjectActivity : AllocatableActivity<Project> does not satisfy this constraint (and neither do all other examples of activities/resources)! Should it be ProjectActivity : AllocatableActivity<ProjectActivity>?

Given your definition of AllocatableResource, why make it generic? You don't use the parameter and it only complicates your task; just make it the base class of your resources and have

public class Allocation<TActivity> 
    where TActivity : AllocatableActivity<TActivity>
{

    public AllocatableResource Resource { get; private set; }
    public TActivity Activity { get; private set; }
    public TimeQuantity Period { get; private set; }

    public Allocation(AllocatableResource resource, TActivity activity, DateTime eventDate, TimeQuantity timeSpent)
    {
        Check.RequireNotNull<IResource>(resource);
        Resource = resource;

        Check.RequireNotNull<Activities.Activity>(activity);
        Activity = activity;

        Check.Require(timeSpent.Amount >= 0, Msgs.Allocation_NegativeTimeSpent);
        TimeSpentPeriod = _toTimeSpentPeriod(eventDate, timeSpent);
    }
}

public class AllocatableActivity<TActivity>
{

    public string Description { get; protected set; }
    public string BusinessId { get; protected set; }

    protected readonly IDictionary<DateTime, Allocation<TActivity>> _allocations;

    public virtual void ClockIn(DateTime eventDate, AllocatableResource resource, TimeQuantity timeSpent)
    {
        var entry = new Allocation<TActivity>(resource, this, eventDate, timeSpent);
        if (_allocations.ContainsKey(eventDate))
        {
            Check.Require(_allocations[eventDate].Resource.Equals(resource),
                      "This method requires that the same resource is the resource in this allocation.");
            _allocations[eventDate] = entry;
        }
        else _allocations.Add(eventDate, entry);
    }
}
Alexey Romanov