Esiste un modello di progettazione per risolvere questo problema?

4

Ho lottato con un progetto quindi ho pensato di chiedere qui e vedere se qualcuno è in grado di aiutarti :)

Panoramica di alto livello

Sto progettando un'applicazione per gamify l'esercizio creando mini competizioni (ad esempio, chi può perdere più peso in 3 mesi o andare in bicicletta più miglia in un mese).

Gli utenti possono creare gare (chiamandole per ora gare) e devono essere in grado di specificare i vincoli - i partecipanti devono essere in una certa fascia di età, oltre un certo peso, il numero massimo di partecipanti in una gara, ecc.

I miei oggetti

RaceParticipant: per brevità, l'unica proprietà pertinente è 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; }
}

Classe astratta RaceConstraint:

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

Implementazione RaceConstraint 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;
    }
}

Implementazione RaceConstraint 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;
    }
}

La domanda

La mia domanda è dove memorizzare MinimumAge, MaximumAge e MaximumNumberOfParticipants. Sto utilizzando il codice Entity Framework in primo luogo, quindi so che non devo preoccuparmi dello schema direttamente, ma devo riflettere su quali proprietà aggiungere a EF e su quale oggetto saranno connesse.

Attualmente EF creerebbe una tabella separata per ogni implementazione di RaceConstraint che sembra eccessivo e sicuramente non voglio che DisplayText e ValidationText siano memorizzati nel db per ogni istanza.

Ho pensato di spostare MinimumAge, MaximumAge e MaximumNumberOfParticipants su proprietà di Race perché ogni razza avrà valori per questi, ma sembra che Race e RaceConstraint siano troppo accoppiati. Mi piacerebbe poter aggiungere un RaceConstraint in futuro quando i requisiti cambiano senza dover aggiungere una proprietà a Race e quindi aggiungere una colonna alla tabella db Race.

Penso che dovrò creare un oggetto contenitore per RaceConstraint che contiene una raccolta di tutti i RaceConstraints possibili. Avrebbe senso inserire queste proprietà in quell'oggetto e quindi rimuovere IEnumerable RaceConstraints da Race e aggiungere invece una proprietà per RaceConstraintContainer?

O forse ci sto pensando troppo e c'è un modello di progettazione che posso seguire. Sono sicuro di non essere la prima persona ad imbattersi in questo scenario!

Grazie in anticipo.

Aggiorna

Dopo averlo rimuginato per alcuni giorni e aver provato ognuna delle risposte, penso che @maple_shaft abbia davvero inchiodato il problema di EF che ostacola la progettazione di OO. Penso di aver trovato un compromesso da qualche parte nel mezzo. Ho aggiunto proprietà come MinimumAge, MaximumAge e MaximumNumberOfParticipants all'oggetto Race perché dal punto di vista del database, queste sono tutte molto correlate e ogni gara avrà voci per ognuna di queste.

Ho modificato la classe astratta RaceConstraint in un'interfaccia che richiede solo il passaggio di PassesConstraint (). Ho fatto un uso migliore di DisplayText facendone un'annotazione dei dati delle proprietà in gara (es. [Visualizza (Nome="Numero massimo di corridori")]) poiché in realtà l'ho voluto solo per le etichette nell'interfaccia utente. Ho spostato il testo da ValidationText a un messaggio di eccezione se un PassesConstraint () ha esito negativo.

Infine, ho creato una classe RaceManager per tenere un elenco di RaceConstraints e gestire l'aggiunta di utenti controllando ogni RaceConstraint. Questa classe gestirà anche la rimozione degli utenti, la determinazione dei vincitori, ecc.

Penso che funzionerà bene anche da un punto di vista MVC perché non sto passando i modelli alla vista che hanno metodi. La classe Race sarà davvero lì solo per contenere i dati EF e renderà un modello pulito da utilizzare per aggiungere o aggiornare gare da una vista.

Grazie per l'aiuto a tutti!

    
posta allen.mn 18.01.2013 - 18:02
fonte

3 risposte

4

Ti stai imbattendo in una limitazione di Entity Framework che incoraggia ciò che ogni tipo di oggetto nel tuo modello di dominio deve mappare direttamente su un singolo costrutto di schema. La creazione di sottotipi di RaceConstraint suona come una buona idea da una prospettiva OO, ma il Modello di dominio anemico è un metodo più incoraggiato modello usando comunque questa struttura.

RaceConstraint dovrebbe essere il tipo di entità e tutte le diverse proprietà di vincolo possibili dovrebbero esistere in questa unica entità. Il testo di visualizzazione e convalida non è ben rappresentato qui e potrebbe essere più appropriato come riferimento a un enum o a un file di risorse poiché ogni singola proprietà di vincolo nel tuo oggetto RaceConstraint potrebbe avere un messaggio univoco.

Tuttavia, per mantenere la progettazione OO, è possibile convertire il modello di dominio in un modello di visualizzazione che non è anemico, tuttavia alcuni considererebbero una pratica scorretta avere una logica aziendale come PassesConstraint nel modello di vista o nel modello di dominio. . Queste sono le due scuole di pensiero su questo.

    
risposta data 18.01.2013 - 18:38
fonte
4

Sembra che tu abbia bisogno di separare i metadati Constraint ( DisplayText , ValidationText ) dai valori per ogni istanza ( MinimumAge , MaximumAge , ecc.)

Se il testo di visualizzazione / convalida non cambierà a meno che tu non stia effettuando comunque aggiornamenti di codice, li dichiarerei solo const e li caricherò da un file di risorse.

MinimumAge, MaximumAge, age MaximumNumberOfParticipants devono rimanere sull'oggetto in cui hanno il maggior significato e la minor duplicazione . Sembra che le sottoclassi RaceConstraint siano un buon posto.

FWIW, stai già utilizzando qualcosa di molto simile al modello di specifica con il metodo PassesConstraint .

    
risposta data 18.01.2013 - 18:29
fonte
4

Vorrei implementare un RangeConstraint con un indicatore del tipo se sei preoccupato per il layout del database, questo ti darà una tabella per i limiti di intervallo e un identificatore (1 = Età, 2 = Peso, 3 = IQ, ecc. ), quindi il minimo e il massimo sono tutto ciò che serve altrimenti. Allora il bit PassesConstraint dovrebbe semplicemente usare un selettore da un dizionario da qualche parte Dictionary<RangeConstraintType, Func<RaceParticipant, int>> rangeSelectors dove gli dai il tipo di vincolo, esso restituisce una funzione che a un partecipante restituirà il valore da validare rispetto all'intervallo. Qualcosa come:

var _rangeSelectors = new Dictionary<RangeConstraintType, Func<RaceParticipant, int>>() {
    { RangeConstraintType.Age, (participant) => participant.Age },
    { RangeConstraintType.Weight, (participant) => participant.Weight },
    ...
};

Quindi nel tuo PassesConstraint è solo ..

public override bool PassesConstraint(Race race, RaceParticipant participant)
{
    var participantsMetric = _rangeSelectors[_rangeConstraintType](participant);
    return participantsMetric < maxValue && patricipantsMetric > minValue;
}
    
risposta data 18.01.2013 - 18:32
fonte

Leggi altre domande sui tag