Utilizzare un hash o un elenco per una raccolta di entità?

2

Ho una classe, che assomiglia a questa:

public class Customer
{
    private readonly IList<Order> _orders = new List<Order>();

    public FirstName FirstName { get; set; }
    public LastName LastName { get; set; }
    public Province Province { get; set; }
    public IEnumerable<Order> Orders 
    {
        get { foreach (var order in _orders) yield return order; }
    }

    internal void AddOrder(Order order)
    {
        _orders.Add(order);
    }
    //Planning to add an AddRange method to add a collection of Orders.
}  

Qualcuno mi ha suggerito di recente che dovrei usare un Set invece di un elenco in quanto non ho bisogno dei vantaggi dell'indice di un elenco poiché è esposto come IEnumberable.

Se lo cambio in un set, come implego l'uguaglianza? Credo di avere alcune opzioni:

1) Lascialo come elenco.

2) Non fare nulla - quindi credo che gli ordini siano unici in base all'uguaglianza referenziale. C'è qualche rischio farlo? Ovunque leggo mi dice che è necessario eseguire l'override di Hashcode e equivale a se si sta utilizzando un Hashset. Ecco un esempio di un Set, in cui l'oggetto Object.hashcode e object.equals di default sembrano essere usati con un HashSet: link

3) Sostituisci .equals e .hashcode nella classe Order in modo che gli Ordini siano uguali se hanno lo stesso ID. Questo link suggerisce che non dovresti farlo: link

4) Creare una classe base Entity simile a questa: link . Il video di YouTube nel punto tre sembra sconsigliarlo.

5) Implementare unCompetitore IEquality. La ricerca che ho fatto suggerisce che questa è una cattiva idea perché: 1) Dovrò iniettare / passare un comparatore nell'entità e 2) Il codice per stabilire se due ordini sono uguali è in una classe diversa da Ordine che rende il modello di dominio anemica.

Sto cercando di seguire il principio del minimo stupore e mi ritrovo a girare in tondo a volte cercando di raggiungere questo obiettivo. Molti dei link qui sopra hanno diversi anni. C'è un modo standard per affrontare questo?

    
posta w0051977 14.04.2018 - 12:58
fonte

2 risposte

3

Permettimi di avvicinarti alla risposta da diverse angolazioni.

Ottimizzazione prematura

Ti è stato detto che ci sono altre collezioni che, a quanto pare, sono più adatte al tuo caso. Probabilmente è vero, ma non trovo gli argomenti forniti sufficienti a farmi credere che List sia del tutto fuori luogo.

Chiediti se HashSet risolve un problema reale e noto dell'attuale implementazione. Chiedi pure se queste capacità di List inutilizzate sono controproducenti. Infine, valutare se lo sforzo di modificare l'implementazione apporti un notevole miglioramento. Ma non passare troppo tempo.

Perdendo troppo tempo a rispondere a queste domande (o rifattorizzarle prematuramente) invece di risolvere il problema, diminuire i benefici di una o di un'altra implementazione. La risoluzione dei problemi che non si verificano è l'ottimizzazione prematura.

Come dice @MetaFight, il tuo tempo è più prezioso dei cicli della CPU.

Parità

Quale uguaglianza significa che devi decidere per te. La lingua ubiquitaria ha molto da dire qui. In caso contrario, chiedi agli esperti del dominio quando due Orders sono uguale a e quando sono esattamente stessi .

Il controllo di queste condizioni non dovrebbe dipendere dal tipo di raccolta che scegli. Un tipo può aiutarti a realizzarlo più facilmente rispetto ad altri, ma non a costo di condizionare il design.

Tieni presente quanto segue. Uguali a e Uguali sono cose diverse.

Due riferimenti potrebbero essere:

  • che punta alla stessa istanza (in memoria)

  • che punta a diverse istanze i cui dati sono uguali . Ciò che causa entrambi hanno lo stesso codice hash.

  • che punta a diverse istanze, con dati diversi e ha ancora lo stesso hashcode (collisione). Raro, ma possibile.

  • che punta a diverse istanze, con dati diversi e che devono essere considerati uguali dal dominio, ma non dalla raccolta.

La delega del controllo di Equality e Same alla raccolta potrebbe non essere sufficiente per esprimere le regole aziendali.

Ad esempio, se avere lo stesso Order due volte nella stessa collezione non è permesso ma avere due equals è, sarebbe opportuno rendere esplicito questo vincolo con le tue classi e funzioni 3 , in modo che altri sviluppatori possano "leggere" ciò che significa uguale / uguale e quando queste caratteristiche sono importanti.

La ragione di tutto questo è semplice. Espressività. 2

Il codice di implementazione per esprimere le differenze tra "same" e "equals" 1 segue il principio del minimo stupore poiché non è necessario per noi avere familiarità con l'SDK.

Non farci cercare nell'SDK per sapere cosa stai cercando di fare.

Test

Breve storia lunga. Se deleghiamo la responsabilità alla raccolta, quando si tratta di test, testeremo la raccolta anziché le regole aziendali.

BACIO

Una raccolta potrebbe essere più adatta di altre. Ma, se List soddisfa i bisogni ed è la soluzione più semplice, allora vai con List . Quale raccolta utilizzare è dettaglio tecnico . Idealmente, il modello di dominio è agnostico a questo tipo di dettagli. Se lo è, è più semplice cambiare l'implementazione da una raccolta all'altra.

1: O qualsiasi altro vincolo del dominio

2: ti suggerisco di leggere la risposta di @ VoiceOfUnreason per ulteriori informazioni su questo argomento

3: o altri elementi del dominio

    
risposta data 14.04.2018 - 18:13
fonte
4

Mi sembra che ti stia aggrovigliando perché stai mescolando qui due diversi livelli di astrazione.

Elenco e HashMap sono strutture dati , sono diversi modi di organizzare le raccolte in memoria (con invarianti leggermente diversi). I tuoi modelli di dominio normalmente non dovrebbero interessarti di questo livello di dettaglio.

Confronta con

private readonly OrderHistory _orders = new OrderHistory();

Il modello per il cliente non ha bisogno di conoscere i dettagli di come gli ordini sono rappresentati in memoria, ha solo bisogno di conoscere la semantica di accesso ad essi.

I tuoi tipi di valore sono in genere in cui inizi a preoccuparti delle rappresentazioni specifiche della memoria. Un buon esercizio per sottolineare questo è il kata di probabilità . Colmano il divario tra la semantica del modello e la alcuni struttura dati che supporta quella semantica.

In altre parole, l'astrazione di OrderHistory dovrebbe isolare il modello dalla decisione che hai preso riguardo alla rappresentazione in memoria da usare. Vedi Parnas su sistemi di decomposizione nei moduli .

Ora, per equality , devi capire la semantica nel tuo modello.

Cioè, se lhs === rhs , quali garanzie sono implicite? Ciò che due cose sono uguali ti dice che, per alcune famiglie di funzioni f , f(lhs) == f(rhs) . Ma la famiglia di funzioni dipende dal tuo modello di dominio.

Per esempio - due storie diverse devono enumerare le loro voci nello stesso ordine? Devono indicare gli stessi oggetti (stesse posizioni in memoria) o solo oggetti equivalenti? (Se gli ordini sono "oggetti valore", probabilmente hai solo bisogno che siano equivalenti).

I vincoli semantici su "uguaglianza" nel tuo modello potrebbero essere più permissivi di quelli forniti dalla tua raccolta di raccolte.

Poiché i dettagli sono incapsulati, hai la possibilità di eseguire tutte le conversioni necessarie. Ad esempio, supponiamo che tu abbia già una funzione isSameHistory(IList<Order> lhs, IList<Order> rhs) e che tu stia verificando l'uguaglianza come

boolean isSameCustomer(Customer lhs, Customer rhs) {
    return isSameHistory(lhs._orders, rhs._orders);
}

e in seguito dovrai implementare nuovamente _orders come array? È disponibile un refactoring piuttosto veloce

boolean isSameCustomer(Customer lhs, Customer rhs) {
    return isSameHistory(lhs._orders.toList(), rhs._orders.toList());
}

Significa che passare da una rappresentazione all'altra in modo equivalente funzionalmente dovrebbe essere semplice. Potrebbe essere necessario eseguire in anticipo alcuni lavori di refactoring per rendere questa modifica facile .

    
risposta data 14.04.2018 - 18:35
fonte

Leggi altre domande sui tag