2009-06-12 4 views
1

Ja, ich weiß, noch eine Frage über veränderbare Objekte. Siehe this für allgemeinen Hintergrund und this für das nächste Analog zu meiner Frage. (obwohl es einige C++ spezifische Obertöne hat, die hier nicht gelten)C# Design für ein Objekt, wo einige Eigenschaften teuer sind: Entschuldigung, um es veränderbar zu machen?

Nehmen wir an, dass der folgende Pseudocode die beste Schnittstelle Design darstellt. Das ist der klarste Ausdruck der Geschäftssemantik (wie sie heute steht) in den OO-Typ. Natürlich unterliegen die UglyData und die Dinge, die wir damit tun, inkrementellen Veränderungen.

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

Was ist die beste Route hier? Optionen kann ich sehen:

  1. veränderbares Objekt mit einem expliziten berechnen() -Methode, wie oben
  2. Mutable Objekt, in die teueren Berechnungen in dem Getter fertig sind (und wahrscheinlich im Cache)
  3. Split in zwei Objekte, bei denen eine inherits (oder vielleicht komponiert?) von der anderen
  4. irgendeine Art von statischen Verriegelungsmechanismus +, wie in der C++ Frage oben

I # bin Neigung in Richtung 2 selbst verbunden. Aber jede Route hat potenzielle Fallstricke.

Wenn Sie # 1 oder # 2 wählen, wie würden Sie Consumer2's Loop über Mutables in einer klaren, korrekten Weise implementieren?

Wenn Sie # 1 oder # 3 wählen, wie würden Sie zukünftige Situationen behandeln, in denen Sie nur einige Eigenschaften berechnen möchten, aber keine anderen? Möchten Sie N Hilfsmethoden/abgeleitete Klassen erstellen?

Wenn Sie # wählen 4, ich glaube, du bist verrückt, aber ich fühle mich frei

+0

Wenn eine Eigenschaft teuer ist, dann läuft sie im Gegensatz zu den MS-Richtlinien, die Eigenschaften und Methoden vergleichen (http://msdn.microsoft.com/en-us/library/bzwdh01d (VS.71)).aspx) aber selbst als Methode hast du natürlich immer noch die gleichen Probleme – annakata

Antwort

1

In Ihrem Fall zu erklären, da Sie LINQ verwenden, sind Sie nur diese Objekte in den Fällen zu konstruieren zu gehen, wo Sie will die Berechnung.

Wenn dies das Standardnutzungsmuster ist, würde ich die teure Berechnung direkt in den Konstruktor einfügen. Die Verwendung der Lazy-Initialisierung ist immer langsamer, es sei denn, Sie planen, dass einige Fälle nicht berechnet werden. Die Berechnung in den Gettern wird nichts speichern (zumindest in diesem speziellen Fall).

Für Mutabilität - veränderbare Objekte mit Referenzsyntax und Identität (dh: Klassen in C#) sind wirklich okay - es ist eher ein Problem, wenn Sie mit veränderbaren Werttypen (dh: Strukturen) zu tun haben. Es gibt viele, viele veränderbare Klassen in .NET BCL - und sie verursachen keine Probleme. Das Problem ist in der Regel mehr von eins, wenn Sie beginnen, mit Werttypen umzugehen. Veränderbare Werttypen führen zu sehr unerwartetem Verhalten.

Im Allgemeinen würde ich diese Frage auf den Kopf stellen - Wie und wo werden Sie dieses Objekt verwenden? Wie können Sie dieses Objekt zum leistungsfähigsten machen (wenn es sich als problematisch herausgestellt hat), ohne die Benutzerfreundlichkeit zu beeinträchtigen? Ihre 1), 3) und 4) Optionen würden alle Benutzerfreundlichkeit leiden lassen, also würde ich sie vermeiden. In diesem Fall hilft 2) nicht. Ich würde es einfach in den Konstruktor einfügen, so dass Ihr Objekt immer in einem gültigen Zustand ist (was sehr gut für Benutzerfreundlichkeit und Wartbarkeit ist).

+0

Klingt im Allgemeinen gut. In dem Code, der mich dazu veranlasste, die Frage zu schreiben, wurden 90% der Instanzen an Orten wie Consumer 1 erstellt, wo ich einfach die Hässlichkeit wegwickle. Nur 10% der Zeit mache ich Berechnungen. Darüber hinaus fragt eine meiner Calc() -Operationen rekursiv nach mehr UglyData ab. Deine Lösung bedeutet also, dass ich meinen freundlichen Wrapper dort überhaupt nicht verwenden kann, oder ich hätte eine Endlosschleife. –

+0

Ging voran und markierte dies als die Antwort. Ich habe Option 3 in meiner App verwendet. Aber meine Motivation war die Grundidee von Reed, dass man immer gültig sein sollte, wenn es einmal konstruiert ist. Also, meine neu abgespeckte FriendlyWrapper und die FriendlyWrapperWithExpensiveStuff-Klasse, die beide erbt, tun alles, was sie in ihren Konstruktoren benötigen. Um die Benutzerfreundlichkeit zu verbessern, verfügt die Expensive-Klasse über einen überladenen Konstruktor, der jede vorhandene Friendly-Klasse akzeptiert. –

Verwandte Themen