Domanda

Voglio fare una domanda su come affrontare un semplice problema di progettazione orientato agli oggetti. Ho alcune mie idee su quale sia il modo migliore per affrontare questo scenario, ma sarei interessato a sentire alcune opinioni della community di Stack Overflow. Sono anche apprezzati i collegamenti ad articoli online pertinenti. Sto usando C #, ma la domanda non è specifica della lingua.

Supponiamo che stia scrivendo un'applicazione di archivio video il cui database ha una tabella Person , con PersonId , Name , DateOfBirth e Address . Ha anche una tabella Staff , che contiene un collegamento a PersonId e una tabella Customer che collega anche a PersonId .

Un semplice approccio orientato agli oggetti sarebbe quello di dire che un Customer " è un " Person e quindi crea classi un po 'come questa:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer : Person {
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff : Person {
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}

Ora possiamo scrivere una funzione per inviare e-mail a tutti i clienti:

static void SendEmailToCustomers(IEnumerable<Person> everyone) { 
    foreach(Person p in everyone)
        if(p is Customer)
            SendEmail(p);
}

Questo sistema funziona bene finché non abbiamo qualcuno che è sia un cliente che un membro dello staff. Supponendo che non vogliamo davvero che il nostro elenco tutti abbia la stessa persona in due volte, una volta come Cliente e una volta come Staff , facciamo una scelta arbitraria tra:

class StaffCustomer : Customer { ...

e

class StaffCustomer : Staff { ...

Ovviamente solo il primo di questi due non infrange la funzione SendEmailToCustomers .

Quindi cosa faresti?

  • Rendi la classe Person dotata di riferimenti opzionali a una classe StaffDetails e CustomerDetails ?
  • Crea una nuova classe che conteneva un Person , oltre a StaffDetails e CustomerDetails ?
  • Rendi tutto un'interfaccia (ad es. IPerson , IStaff , ICustomer ) e crea tre classi che implementano le interfacce appropriate?
  • Hai un altro approccio completamente diverso?
È stato utile?

Soluzione

Mark, questa è una domanda interessante. Troverai tante opinioni su questo. Non credo che esista una risposta "giusta". Questo è un ottimo esempio di dove una rigida progettazione gerarchica di oggetti può davvero causare problemi dopo la creazione di un sistema.

Ad esempio, supponiamo che tu abbia scelto il " Cliente " e " Staff " classi. Distribuisci il tuo sistema e tutto è felice. Alcune settimane dopo, qualcuno sottolinea che sono sia "dipendenti" che "clienti" e non ricevono le e-mail dei clienti. In questo caso, devi apportare molte modifiche al codice (riprogettazione, non re-fattore).

Credo che sarebbe eccessivamente complesso e difficile da mantenere se si tenta di avere una serie di classi derivate che implementano tutte le permutazioni e la combinazione di persone e i loro ruoli. Ciò è particolarmente vero dato che l'esempio sopra è molto semplice: nella maggior parte delle applicazioni reali, le cose saranno più complesse.

Per il tuo esempio qui, vorrei scegliere " Prendere un altro approccio completamente diverso " ;. Implementerei la classe Person e includerei in essa una raccolta di "ruoli". Ogni persona potrebbe avere uno o più ruoli come "Cliente", "Personale", e "Venditore".

Ciò renderà più semplice l'aggiunta di ruoli quando vengono scoperti nuovi requisiti. Ad esempio, potresti semplicemente avere una base "Ruolo" classe, e ne derivano nuovi ruoli.

Altri suggerimenti

Potresti prendere in considerazione l'utilizzo dei Pattern di parti e responsabilità

In questo modo la Persona avrà una raccolta di Responsabilità che possono essere di tipo Cliente o Personale.

Il modello sarà anche più semplice se aggiungi più tipi di relazione in seguito.

L'approccio puro sarebbe: rendere tutto un'interfaccia. Come dettagli di implementazione, puoi facoltativamente utilizzare una qualsiasi delle varie forme di composizione o ereditarietà di implementazione. Dal momento che si tratta di dettagli di implementazione, non contano per l'API pubblica, quindi sei libero di scegliere quale ti semplifica la vita.

Una persona è un essere umano, mentre un cliente è solo un ruolo che una persona può assumere di volta in volta. L'uomo e la donna sarebbero candidati per ereditare la Persona, ma il Cliente è un concetto diverso.

Il principio di sostituzione di Liskov dice che dobbiamo essere in grado di usare classi derivate in cui abbiamo riferimenti a una classe base, senza saperlo. Avere il cliente ereditare la persona violerebbe questo. Un cliente potrebbe forse anche essere un ruolo svolto da un'organizzazione.

Fammi sapere se ho capito correttamente la risposta di Foredecker. Ecco il mio codice (in Python; scusa, non conosco C #). L'unica differenza è che non notificherei qualcosa se una persona "è un cliente", lo farei se uno dei suoi ruoli "è interessato a" quella cosa. È abbastanza flessibile?

# --------- PERSON ----------------

class Person:
    def __init__(self, personId, name, dateOfBirth, address):
        self.personId = personId
        self.name = name
        self.dateOfBirth = dateOfBirth
        self.address = address
        self.roles = []

    def addRole(self, role):
        self.roles.append(role)

    def interestedIn(self, subject):
        for role in self.roles:
            if role.interestedIn(subject):
                return True
        return False

    def sendEmail(self, email):
        # send the email
        print "Sent email to", self.name

# --------- ROLE ----------------

NEW_DVDS = 1
NEW_SCHEDULE = 2

class Role:
    def __init__(self):
        self.interests = []

    def interestedIn(self, subject):
        return subject in self.interests

class CustomerRole(Role):
    def __init__(self, customerId, joinedDate):
        self.customerId = customerId
        self.joinedDate = joinedDate
        self.interests.append(NEW_DVDS)

class StaffRole(Role):
    def __init__(self, staffId, jobTitle):
        self.staffId = staffId
        self.jobTitle = jobTitle
        self.interests.append(NEW_SCHEDULE)

# --------- NOTIFY STUFF ----------------

def notifyNewDVDs(emailWithTitles):
    for person in persons:
        if person.interestedIn(NEW_DVDS):
            person.sendEmail(emailWithTitles)

Eviterei che " sia " spunta (" istanza di " in Java). Una soluzione è usare un Decorator Pattern . È possibile creare un EmailablePerson che decora Person in cui EmailablePerson utilizza la composizione per contenere un'istanza privata di un Person e delega tutti i metodi non e-mail all'oggetto Person.

Abbiamo studiato questo problema al college l'anno scorso, stavamo imparando eiffel quindi abbiamo usato l'ereditarietà multipla. Comunque l'alternativa ai ruoli di Foredecker sembra essere abbastanza flessibile.

Cosa c'è di sbagliato nell'invio di un'e-mail a un cliente che è un membro dello staff? Se è un cliente, può essere inviato l'e-mail. Sbaglio nel pensare così? E perché dovresti prendere " tutti " come la tua mailing list? Ma sarebbe meglio avere un elenco di clienti poiché abbiamo a che fare con " sendEmailToCustomer " metodo e non " sendEmailToEveryone " metodo? Anche se desideri utilizzare " tutti " elenco non è possibile consentire duplicati in tale elenco.

Se nessuno di questi è realizzabile con un sacco di redisgn, andrò con la prima risposta Foredecker e potrebbe essere che dovresti avere alcuni ruoli assegnati a ogni persona.

Le tue classi sono solo strutture di dati: nessuna di esse ha alcun comportamento, solo getter e setter. L'ereditarietà non è appropriata qui.

Adotta un altro approccio completamente diverso: il problema con la classe StaffCustomer è che il tuo membro del personale potrebbe iniziare come solo personale e diventare un cliente in seguito, quindi dovrai eliminarlo come personale e creare una nuova istanza della classe StaffCustomer . Forse un semplice booleano nella classe Staff di "isCustomer" consentirebbe alla nostra lista di tutti (presumibilmente compilata da ottenere tutti i clienti e tutto il personale dalle tabelle appropriate) di non ottenere il membro del personale in quanto saprà che è già stato incluso come cliente.

Ecco alcuni altri suggerimenti: Dalla categoria di & # 8220; non pensare nemmeno di farlo & # 8221; ecco alcuni cattivi esempi di codice riscontrato:

Il metodo Finder restituisce Object

Problema: a seconda del numero di occorrenze rilevate, il metodo finder restituisce un numero che rappresenta il numero di occorrenze & # 8211; o! Se solo uno trovato restituisce l'oggetto reale.

Non farlo! Questa è una delle peggiori pratiche di codifica e introduce ambiguità e rovina il codice in modo tale che quando uno sviluppatore diverso entra in gioco lei o lui ti odierà per questo.

Soluzione: se sono necessarie tali 2 funzionalità: il conteggio e il recupero di un'istanza creano 2 metodi uno che restituisce il conteggio e uno che restituisce l'istanza, ma mai un singolo metodo che fa entrambe le cose.

Problema: una cattiva pratica derivata è quando un metodo finder restituisce una singola occorrenza trovata o una matrice di occorrenze se ne viene trovata più di una. Questo pigro stile di programmazione è fatto molto dai programmatori che fanno il precedente in generale.

Soluzione: avendo questo tra le mani, restituirei una matrice di lunghezza 1 (una) se viene trovata una sola occorrenza e una matrice con lunghezza > 1 se vengono rilevate più occorrenze. Inoltre, non trovare alcuna occorrenza restituirebbe null o un array di lunghezza 0 a seconda dell'applicazione.

Programmazione su un'interfaccia e utilizzo di tipi di ritorno covarianti

Problema: programmazione su un'interfaccia e utilizzo di tipi di ritorno covarianti e trasmissione nel codice chiamante.

Soluzione: utilizzare invece lo stesso supertipo definito nell'interfaccia per definire la variabile che dovrebbe puntare al valore restituito. Ciò mantiene la programmazione su un approccio di interfaccia e il codice pulito.

Le classi con più di 1000 righe rappresentano un pericolo in agguato Anche i metodi con più di 100 linee sono in agguato!

Problema: alcuni sviluppatori inseriscono troppe funzionalità in una classe / metodo, essendo troppo pigri per interrompere la funzionalità & # 8211; questo porta a una bassa coesione e forse ad un alto accoppiamento & # 8211; l'inverso di un principio molto importante in OOP! Soluzione: evitare di usare troppe classi interne / nidificate & # 8211; queste classi devono essere utilizzate SOLO in base alle necessità, non devi fare l'abitudine di usarle! Usarli potrebbe portare a più problemi come limitare l'eredità. Cerca il codice duplicato! Lo stesso codice o troppo simile potrebbe già esistere in qualche implementazione di supertipo o forse in un'altra classe. Se è in un'altra classe che non è un supertipo, hai anche violato la regola di coesione. Fai attenzione ai metodi statici & # 8211; forse hai bisogno di una classe di utilità da aggiungere!
Più a: http://centraladvisor.com/it/oop-what -sono-the-best-practice-in-oop

Probabilmente non vuoi usare l'ereditarietà per questo. Prova invece questo:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer{
    public Person PersonInfo;
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff {
    public Person PersonInfo;
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top