C # progetto di un oggetto in cui alcune proprietà sono costosi: scusa per rendere mutevole?

StackOverflow https://stackoverflow.com/questions/987461

  •  13-09-2019
  •  | 
  •  

Domanda

Sì, lo so, l'ennesima domanda su oggetti mutabili. Vedi questo per il contesto generale e questo per l'analogo più vicino alla mia domanda. (Anche se ha un po 'di C ++ connotazioni specifiche che non si applicano qui)

Supponiamo che il seguente pseudo codice rappresenta la migliore interfaccia di progettazione. Cioè, è l'espressione più chiara della semantica di business (come sono oggi) in tipo OO. Naturalmente, l'UglyData e le cose che stiamo compito di fare con essa sono soggette a cambiamento incrementale.

public class FriendlyWrapper
{
    public FriendlyWrapper(UglyDatum u)
    {
        Foo = u.asdf[0].f[0].o.o;
        Bar = u.barbarbar.ToDooDad();
        Baz = u.uglyNameForBaz;
        // etc
    }

    public Widget Foo { get; private set; }
    public DooDad Bar { get; private set; }
    public DooDad Baz { get; private set; }
    // etc
    public WhizBang Expensive1 { get; private set; }
    public WhizBang Expensive2 { get; private set; }

    public void Calculate()
    {
        Expensive1 = Calc(Foo, Bar);
        Expensive2 = Calc(Foo, Baz);
    }

    private WhizBang Calc(Widget a, DooDad b) { /* stuff */ }

    public override void ToString()
    {
        return string.Format("{0}{1}{2}{3}{4}", Foo, Bar, Baz, Expensive1 ?? "", Expensive2 ?? "");                             
    }
}

// Consumer 1 is happy to work with just the basic wrapped properties
public string Summarize()
{
    var myStuff = from u in data
                  where IsWhatIWant(u)
                  select new FriendlyWrapper(u);

    var sb = new StringBuilder();
    foreach (var s in myStuff)
    {
        sb.AppendLine(s.ToString());
    }
    return sb.ToString();
}

// Consumer 2's job is to take the performance hit up front.  His callers might do things 
// with expensive properties (eg bind one to a UI element) that should not take noticeable time. 
public IEnumerable<FriendlyWrapper> FetchAllData(Predicate<UglyDatum> pred)
{
    var myStuff = from u in data
                  where pred(u)
                  select new FriendlyWrapper(u);

    foreach (var s in myStuff)
    {
        s.Calculate();  // as written, this doesn't do what you intend...
    }

    return myStuff;
}

Qual è la strada migliore qui? Opzioni posso vedere:

  1. oggetto mutabile con un metodo esplicito Calcolare (), come sopra
  2. oggetto mutabile in cui i calcoli costosi sono fatti nei getter (e probabilmente nella cache)
  3. Split in due oggetti in cui si eredita (o forse compone?) Dall'altro
  4. Una sorta di meccanismo statico + bloccaggio, come nella questione C ++ collegato sopra

Sono sporgendosi verso # 2 me stesso. Ma ogni percorso ha potenziali insidie.

Se si sceglie # 1 o 2 #, allora come si potrebbe implementare ciclo di Consumer2 sopra mutables in modo chiaro e corretto?

Se si sceglie # 1 o 3 #, come si dovrebbe gestire le situazioni future in cui si desidera solo per calcolare alcune proprietà ma non altri? Disposti a creare metodi helper N / classi derivate?

Se si sceglie # 4, penso che tu sia pazzo, ma sentitevi liberi di spiegare

È stato utile?

Soluzione

Nel tuo caso, dal momento che si sta utilizzando LINQ, si sta solo andando a costruire questi oggetti nei casi in cui si desidera che il calcolo.

Se questo è il vostro modello di utilizzo normale, vorrei solo mettere il calcolo costoso direttamente nel costruttore. Utilizzando l'inizializzazione differita è sempre più lenta a meno che non si prevede di avere alcuni casi in cui non calcolare. Facendo il calcolo dei getter non salverà nulla (almeno in questo caso specifico).

Per quanto riguarda la mutevolezza - oggetti mutabili con la sintassi di riferimento e di identità (ad esempio: classi in C #) sono veramente bene - è più un problema quando hai a che fare con tipi di valore mutabili (ad esempio: struct). Ci sono molte, molte classi mutevoli in .NET BCL - e non causano problemi. Il problema è in genere più di uno quando si inizia a trattare con i tipi di valore. i tipi di valore mutabili portano ad un comportamento molto inaspettato.

In generale, mi piacerebbe girare questa domanda a testa in giù - Come e dove stai andando a utilizzare questo oggetto? Come si può fare questo oggetto il più performante (se è stato determinato a essere problematico) senza compromettere l'usabilità? Il tuo 1), 3) e 4) le opzioni sarebbero tutti fare usabilità soffrire, quindi mi piacerebbe evitarli. In questo caso, facendo 2) non aiuterà. Avevo appena messo nel costruttore, quindi l'oggetto è sempre in uno stato valido (che è molto buono per l'usabilità e manutenibilità).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top