Utilizzo di più "sottotipi" rispetto a un singolo tipo più grande?

4

Ho un modello user in un'applicazione a cui sto lavorando, che attualmente usa sub-types per incapsulare properties a seconda del tipo di utente che sei: non posso fare a meno di pensare che questo è davvero un pessimo design.

Per espandere più in dettaglio, un User nella nostra Applicazione può essere un Istruttore o uno Studente, e di seguito viene mostrato come questo è attualmente modellato:

public class User
{
    public string EMail {get; set;}

    public string DisplayName {get; set;}

    public Student StudentObject {get; set;}

    public Instructor InstructorObject {get; set;}

    public void Login()
    {
        //...
    }

    public void BookAppointment()
    {
        //...
    }

    //Other User-Level Behaviours

    public class Instructor
    {
        public TimeSpan TypicalDayStart {get; set;}
        public TimeSpan TypicalDayEnd {get; set;}
        public ObservaleCollection Students {get; set;}

        //No Instructor Specific Behaviour / Methods
    }

    public class Student
    {
        public string CurrentGrade {get; set;}
        public DateTime NextExamDate {get; set;}
        public bool ExamPassed {get; set;}

        public int InstructorID {get; set;}

        //No Student Specific Behaviour /Methods
    }
}

Per me, questo davvero odora. Ma non riesco a pensare a un modo migliore per modellare i dati:

  • Separare Instructor / Student classi sembra essere la strada sbagliata, in quanto non sono in realtà entità separate, sono solo un insieme di proprietà che sono uniche per i diversi tipi di l'utente che gestisce la nostra applicazione.
  • Rimuovere i sottotipi e gestire tutti i dati di una classe sembra essere il modo in cui sono incline ad andare. Tuttavia questo rende alcuni properties ridondanti. Inoltre non è molto a prova di futuro ... Solo perché non esiste un comportamento specifico per istruttore / studente al momento non significa che non ci sarà in futuro.
posta KidCode 13.03.2016 - 14:12
fonte

3 risposte

5

A prima vista, ho pensato che si trattasse di un design terribile, perché un Instructor è un User e quindi è un Student , e tale è una relazione che supplicherebbe eredità.

Ma pensandoci due volte, mi sono reso conto che Instructor e Student non sono Users , ma Roles che un utente potrebbe avere . Questo richiederebbe la composizione come nel tuo attuale design. E un utente può eventualmente avere più ruoli contemporaneamente.

Questo modello di composizione solleva comunque due domande:

  • ci sono proprietà che appaiono in diversi ruoli? In questo caso, devi chiarire se è correlato al User come persona (ad es. Indirizzo, ...) o al suo ruolo (ad es. Orario di lavoro). Nel primo caso questi devono essere presi in considerazione nella classe User .
  • Potresti avere il vantaggio di usare una classe astratta Role e rendere Student e Instructor ereditari da esso? Ciò potrebbe consentire, ad esempio, di utilizzare un contenitore di ruolo anziché cablare l'utente a ruoli fissi predeterminati. Questo potrebbe accumulare più volte lo stesso ruolo (ad esempio un istruttore con più classi, ciascuno con studenti diversi) o se ci sono molti più ruoli rispetto ai due che ci hai mostrato.

    
risposta data 13.03.2016 - 19:50
fonte
2

Sembra che tu stia tentando di costruire un modello di applicazione attorno alle relazioni tra le tue entità; tuttavia suggerirei che un approccio migliore sarebbe quello di separare il modello dati e il modello dell'applicazione.

Il modello dell'applicazione dovrebbe essere guidato dal comportamento dell'applicazione; mentre il modello dei dati dovrebbe essere guidato dalle relazioni tra le tue entità.

Se eviti di lasciare che le relazioni di entità informino il tuo modello di applicazione, allora il tuo modello di applicazione è libero di evolversi attorno a schemi e strutture che supportano il suo comportamento.

Una possibile soluzione per il tuo Istruttore / Studente / Utente Entità Dati potrebbe essere quella di impostare User come attributo di Instructor e Student .

public class Student
{
    User UserData { get; set; }

    public string CurrentGrade { get; set; }
    public DateTime NextExamDate { get; set; }
    public bool ExamPassed { get; set; }
}

public class Instructor
{
    User UserData { get; set; }

    public TimeSpan TypicalDayStart { get; set; }
    public TimeSpan TypicalDayEnd { get; set; }
    public ObservableCollection<Student> Students { get; set; }
}

Anche l'ereditarietà è una soluzione valida (in particolare se stai usando un ORM che supporta l'ereditarietà, allora potrebbe anche essere una soluzione preferita); tuttavia, l'obiettivo principale della modellazione dei dati è razionalizzare i dati e non supportare il comportamento di un'applicazione specifica. (Ricorda che nei sistemi più grandi, un singolo modello di dati deve servire molte applicazioni).

Probabilmente vorrai includere una classe generale DataModel che gestisca la persistenza della tua data (ad esempio se si tratta di un file, quindi Salva / Carica, o se si tratta di un Database / ORM, quindi della stringa di connessione, ecc.); la classe DataModel servirebbe efficacemente il tuo modello di dati razionalizzato al resto dell'applicazione; le stesse entità di dati non avrebbero alcun comportamento specifico per l'applicazione (anche se alcuni comportamenti generici come la convalida vanno bene).

La modellazione dell'applicazione è uno scenario completamente diverso; la tua applicazione riguarda il comportamento come Login e BookAppointment - nessuno dei quali suggerisce immediatamente classi con nomi come Instructor o Student .

Per questi, potresti prendere in considerazione entità come UserAuthenticator e Calendar . Queste entità possono avere riferimenti al modello di dati razionalizzato, ma il modello di dati (con qualsiasi relazione di entità complessa che deve rappresentare in modo che l'applicazione possa interrogarlo e aggiornarlo) rimarrebbe da solo.

public class Calendar
{
    private DataModel _dataModel;

    public Calendar(DataModel dataModel)
    {
        _dataModel = dataModel;
    }

    public void BookAppointment(DateTime date, TimeSpan duration)
    {
    }
}

public class Authenticator
{
    private DataModel _dataModel;

    public Authenticator(DataModel dataModel)
    {
        _dataModel = dataModel;
    }

    public void Login(string username, string password)
    {
    }
}

Potresti collegarlo tutti insieme nella directory principale / principale della tua app, ad esempio:

public static void Main(string [] args)
{
    var dataModel = new DataModel();
    dataModel.Connect("MyDatabase");

    var calendar = new Calendar(dataModel);
    var authenticator = new Authenticator(dataModel);

    var mainApp = new MainApp(calendar, authenticator);
    mainApp.Run();
}
    
risposta data 13.03.2016 - 20:02
fonte
0

È difficile rispondere a questa domanda senza conoscere ulteriori dettagli su cosa farà o farà il tuo programma in futuro, ma sono d'accordo che probabilmente non è questa la strada da percorrere.

Il punto di avere una singola classe utente è che tu possa accedere ai dati ed eseguire azioni che abbiano senso per tutti i tipi di utenti, anche per quelli che sono studenti, istruttori, bidelli e quant'altro allo stesso tempo (in alcune applicazioni potrebbero essere ruoli che si escludono a vicenda, ma come hai sottolineato non è chiaro che siano nel tuo caso). Ad esempio, bookAppointment([user1, user2, user3], time, place) probabilmente ha senso per tutti i tipi di utenti, ma calculateGPA(user) probabilmente non ha alcun senso sugli utenti non studenti. Ciò implica che la classe utente deve contenere informazioni sulla pianificazione e sulla posizione in un formato che funzioni per tutti i tipi di utente, ma non una qualsiasi delle informazioni di valutazione o esame che semplicemente non ha senso per un utente non studente. Se ti trovi in una situazione in cui hai bisogno del GPA ma ti viene dato solo un utente, dovrebbe esserci un passo esplicito "posso ottenere una classe dello studente corrispondente a questo utente" piuttosto che un metodo calculateGPA(User) che restituisce o restituisce null se l'utente è di tipo sbagliato, poiché ciò significa che non puoi dimenticare di gestire correttamente il caso non studente.

La grande domanda a cui probabilmente non posso rispondere è se Studente e Istruttore dovrebbero essere sottoclassi di Utente, o in gran parte tipi separati che vengono recuperati utilizzando alcuni ID nella classe Utente quando necessario per operazioni specifiche.

Il vantaggio di renderli sottoclassi è che se e quando si desidera un comportamento diverso per diversi tipi di utenti, si usa solo il polimorfismo. Ad esempio, supponiamo che tu voglia bookAppointment([user1, user2], nextSaturday) fallire se gli utenti sono Studenti ma non se gli utenti sono Istruttori. Mi aspetto che questo implichi un metodo come User.getDefaultAvailability(day) che per gli studenti restituisce false nei fine settimana e che gli istruttori restituiscono true.

Lo svantaggio di renderli sottoclassi è che non puoi mai avere un utente di più tipi senza incorrere in tutti i soliti problemi di ereditarietà multipla. Questo va bene se puoi essere sicuro che non avrai mai bisogno che un utente sia contemporaneamente di entrambi i tipi (ad es., Anche se qualcuno finirà con entrambi i ruoli nel mondo reale, gli verranno solo assegnati due account utente separati). In caso contrario, potrebbe essere preferibile fornire all'Utente alcuni campi IDCID / InstructorID / etc nullable, quindi avere User.getDefaultAvailability() verificare esplicitamente quale di questi ID sia null per decidere se chiamare isWeekend(day) o semplicemente restituire true. In questo modo puoi decidere in modo esplicito quale set di regole applicare quando un utente si adatta a più tipi, piuttosto che essere vincolato da come la tua lingua gestisce l'MI (se lo gestisce affatto).

P.S. Suppongo che l'aggiunta di un nuovo tipo di utente sarà molto rara e, se dovesse accadere, verrà fornita con alcuni requisiti non banali, ad esempio argomenti su come scrivere una nuova classe di tipo utente senza modificare la classe User non è terribilmente importante.

    
risposta data 13.03.2016 - 15:38
fonte

Leggi altre domande sui tag