C# Design Patterns – Is There a Design Pattern to Solve This Problem?

cdesigndesign-patterns

I've been struggling with a design so I figured I'd ask here and see if anyone's able to help 🙂

High level overview

I'm designing an app to gamify exercise by creating mini competitions (ex. Who can lose the most weight in 3 months or bike the most miles in a month).

Users can create competitions (calling them races for now) and they need to be able to specify constraints – participants have to be in a certain age range, over a certain weight, maximum number of participants in a race, etc.

My Objects

RaceParticipant – for brevity, the only pertinent property is int Age.

Race:

public class Race
{
    public virtual int RaceId { get; set; }
    public virtual string RaceName { get; set; }
    public virtual int RaceTypeId { get; set; }
    public virtual TrainingType RaceType { get; set; }
    public virtual IEnumerable<RaceParticipant> RaceParticipants { get; set; }
    public virtual IEnumerable<RaceConstraint> RaceConstraints { get; set; }
}

RaceConstraint abstract class:

public abstract class RaceConstraint
{
    public string DisplayText { get; set; }
    public string ValidationText { get; set; }
    public virtual bool PassesConstraint(Race race, RaceParticipant participant); 
}

RaceConstraint implementation MaxNumberOfParticipants:

public class MaxNumberOfParticipants : RaceConstraint
{
    public int MaximumNumberOfParticipants { get; set; }

    public MaxNumberOfParticipants()
    {
        DisplayText = "Maximum number of racers";
        ValidationText = "This race has reached the maximum number of racers.";
    }

    public override bool PassesConstraint(Race race, RaceParticipant participant) 
    {
        bool result = false;
        if (race.RaceParticipants.Count() <= MaximumNumberOfParticipants)
        {
            result = true;
        }
        return result;
    }
}

RaceConstraint implementation AgeRange:

public class AgeRange : RaceConstraint
{
    public int MinimumAge { get; set; }
    public int MaximumAge { get; set; }

    public AgeRange()
    {
        DisplayText = "Age range of racers";
        ValidationText = string.Format("Racers must be between the ages of {0} and {1}.", MinimumAge, MaximumAge);
    }

    public override bool PassesConstraint(Race race, RaceParticipant participant)
    {
        bool result = false;
        if (MinimumAge <= participant.Age && participant.Age <= MaximumAge)
        {
            result = true;
        }
        return result;
    }
}

The question

My question is where to store MinimumAge, MaximumAge, and MaximumNumberOfParticipants. I'm using Entity Framework code-first so I know I don't need to worry about the schema directly, but I do need to think about which properties to add to EF and which object they'll be connected to.

Currently EF would make a separate table for each implementation of RaceConstraint which seems like overkill and I definitely don't want DisplayText and ValidationText stored in the db for each instance.

I thought about moving MinimumAge, MaximumAge, and MaximumNumberOfParticipants to properties of Race because each race will have values for these, but that feels like Race and RaceConstraint would be too coupled. I'd like to be able to add a RaceConstraint in the future as requirements change without having to add a property to Race and therefor add a column to the Race db table.

I think I'm going to have to make a container object for RaceConstraint that holds a collection of all possible RaceConstraints. Would it make sense to put these properties in that object and then remove IEnumerable RaceConstraints from Race and instead add a property for the RaceConstraintContainer?

Or maybe I'm way over thinking this and there's a design pattern I can follow. I'm sure I'm not the first person to come across this scenario!

Thanks in advance.

UPDATE

After mulling this over for a few days and trying out each of the answers, I think @maple_shaft really nailed the problem of EF getting in the way of OO design. I think I found a compromise somewhere in the middle. I added properties like MinimumAge, MaximumAge, and MaximumNumberOfParticipants to the Race object because from a database perspective, these are all very related and each race will have entries for each of these.

I changed the abstract class RaceConstraint to an interface that just requires bool PassesConstraint(). I made better use of DisplayText by making it a data annotation of the properties in race (ex. [Display(Name = "Maximum number of racers")]) since I really only wanted it for labels in the UI. I moved the text from ValidationText to an exception message if a PassesConstraint() fails.

Finally, I made a RaceManager class to hold a List of RaceConstraints and handle adding users by checking each RaceConstraint. This class will also handle removing users, determining winners, etc.

I think this will work well from an MVC standpoint too because I'm not passing models to the view that have methods. The Race class will really just be there to hold EF data and will make for a clean model to use for adding or updating races from a view.

Thanks for the help everyone!

Best Answer

You are running into a limitation of Entity Framework that encourages what each type of object in your domain model to map directly to a single schema construct. Creating sub types of RaceConstraint sounds like a good idea from an OO perspective but then the Anemic Domain Model is a more encouraged pattern using this framework anyway.

RaceConstraint should be the entity type and all the different possible constraint properties should exist in this one entity. Display and Validation text are not well represented here, and may be more appropriate as references to an enum or a resource file as each individual constraint property in your RaceConstraint object may have a unique message.

To maintain the OO design however, you can translate your domain model to a view model that is not Anemic, however some would consider it bad practice to have business logic like PassesConstraint in your View Model or your Domain Model as well. These are the two schools of thought on this.

Related Topic