Ich muss eine Klassenhierarchie für mein C # -Projekt entwerfen. Grundsätzlich ähneln die Klassenfunktionen den WinForms-Klassen. Nehmen wir als Beispiel das WinForms-Toolkit. (Ich kann jedoch weder WinForms noch WPF verwenden.)
Es gibt einige grundlegende Eigenschaften und Funktionen, die jede Klasse bereitstellen muss. Maße, Position, Farbe, Sichtbarkeit (wahr / falsch), Zeichenmethode usw.
Ich brauche Designberatung Ich habe ein Design mit einer abstrakten Basisklasse und Schnittstellen verwendet, die keine echten Typen sind, sondern eher Verhaltensweisen. Ist das ein gutes Design? Wenn nicht, was wäre ein besseres Design.
Der Code sieht folgendermaßen aus:
abstract class Control
{
public int Width { get; set; }
public int Height { get; set; }
public int BackColor { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int BorderWidth { get; set; }
public int BorderColor { get; set; }
public bool Visible { get; set; }
public Rectangle ClipRectange { get; protected set; }
abstract public void Draw();
}
Einige Steuerelemente können andere Steuerelemente enthalten, andere können nur (als untergeordnete Elemente) enthalten sein. Daher denke ich darüber nach, zwei Schnittstellen für diese Funktionen zu erstellen:
interface IChild
{
IContainer Parent { get; set; }
}
internal interface IContainer
{
void AddChild<T>(T child) where T : IChild;
void RemoveChild<T>(T child) where T : IChild;
IChild GetChild(int index);
}
WinForms steuert den Anzeigetext, sodass dies auch in die Benutzeroberfläche eingeht:
interface ITextHolder
{
string Text { get; set; }
int TextPositionX { get; set; }
int TextPositionY { get; set; }
int TextWidth { get; }
int TextHeight { get; }
void DrawText();
}
Einige Steuerelemente können in ihrem übergeordneten Steuerelement angedockt werden.
enum Docking
{
None, Left, Right, Top, Bottom, Fill
}
interface IDockable
{
Docking Dock { get; set; }
}
... und jetzt erstellen wir einige konkrete Klassen:
class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}
Das erste "Problem", das mir hier einfällt, ist, dass die Schnittstellen nach ihrer Veröffentlichung im Grunde genommen in Stein gemeißelt sind. Nehmen wir jedoch an, dass ich meine Benutzeroberflächen so gut gestalten kann, dass sie in Zukunft nicht mehr geändert werden müssen.
Ein weiteres Problem, das ich in diesem Entwurf sehe, ist, dass jede dieser Klassen ihre Schnittstellen implementieren müsste und dass eine Vervielfältigung von Code schnell erfolgen wird. Beispielsweise wird in Label und Button die DrawText () -Methode von der ITextHolder-Schnittstelle oder von jeder Klasse abgeleitet, die aus der IContainer-Verwaltung von untergeordneten Elementen stammt.
Meine Lösung für dieses Problem besteht darin, diese "duplizierten" Funktionen in dedizierten Adaptern zu implementieren und Aufrufe an diese weiterzuleiten. Daher hätten sowohl Label als auch Button einen TextHolderAdapter-Member, der innerhalb von Methoden aufgerufen würde, die von der ITextHolder-Schnittstelle geerbt wurden.
Ich denke, dieses Design sollte mich davor schützen, dass ich zu viele gemeinsame Funktionen in der Basisklasse habe, die mit virtuellen Methoden und unnötigem "Rauschcode" schnell aufgebläht werden könnten. Verhaltensänderungen würden durch Erweitern von Adaptern und nicht durch von Steuerelementen abgeleitete Klassen erreicht.
Ich denke, dies wird das "Strategie" -Muster genannt, und obwohl es zu diesem Thema Millionen von Fragen und Antworten gibt, möchte ich Sie um Ihre Meinung bitten, was ich für dieses Design in Betracht ziehe und an welche Fehler Sie denken können mein Ansatz.
Ich sollte hinzufügen, dass es eine fast 100% ige Chance gibt, dass zukünftige Anforderungen neue Klassen und neue Funktionalitäten erfordern.
quelle
System.ComponentModel.Component
oderSystem.Windows.Forms.Control
einer der anderen vorhandenen Basisklassen erben ? Warum müssen Sie eine eigene Steuerelementhierarchie erstellen und alle diese Funktionen neu definieren?IChild
scheint wie ein schrecklicher Name.Antworten:
[1] Füge virtuelle "Getter" & "Setter" zu deinen Eigenschaften hinzu, ich musste eine andere Kontrollbibliothek hacken, da ich diese Funktion benötige:
Ich weiß, dass dieser Vorschlag "ausführlicher" oder komplexer ist, aber in der Praxis sehr nützlich.
[2] Fügen Sie eine "IsEnabled" -Eigenschaft hinzu und verwechseln Sie sie nicht mit "IsReadOnly":
Bedeutet, dass Sie möglicherweise Ihre Kontrolle zeigen müssen, aber keine Informationen anzeigen.
[3] Fügen Sie eine "IsReadOnly" -Eigenschaft hinzu und verwechseln Sie sie nicht mit "IsEnabled":
Das bedeutet, dass das Steuerelement Informationen anzeigen kann, aber vom Benutzer nicht geändert werden kann.
quelle
Gute Frage! Das Erste, was mir auffällt, ist, dass die Zeichenmethode keine Parameter hat. Wo zeichnet sie? Ich denke, es sollte ein Surface- oder Graphics-Objekt als Parameter erhalten (als Schnittstelle natürlich).
Eine weitere Sache, die ich merkwürdig finde, ist das IContainer-Interface. Es hat
GetChild
Methode, die ein einzelnes untergeordnetes Element zurückgibt und keine Parameter enthält. Möglicherweise sollte eine Auflistung zurückgegeben werden, oder wenn Sie die Draw-Methode in eine Schnittstelle verschieben, kann diese Schnittstelle implementiert werden und eine Draw-Methode verwendet werden, mit der die interne untergeordnete Auflistung gezeichnet werden kann, ohne sie verfügbar zu machen .Vielleicht können Sie sich für Ideen auch WPF ansehen - es ist meiner Meinung nach ein sehr gut gestalteter Rahmen. Sie können sich auch die Entwurfsmuster für Komposition und Dekorateur ansehen. Der erste kann für Containerelemente und der zweite für das Hinzufügen von Funktionen zu Elementen nützlich sein. Ich kann nicht sagen, wie gut das auf den ersten Blick funktionieren wird, aber ich denke Folgendes:
Um die Duplizierung zu vermeiden, könnten Sie so etwas wie primitive Elemente haben - wie das Textelement. Dann können Sie die komplizierteren Elemente mit diesen Grundelementen erstellen. Zum Beispiel a
Border
ein Element, das ein einzelnes untergeordnetes Element hat. Wenn Sie seineDraw
Methode aufrufen , zeichnet es einen Rahmen und einen Hintergrund und anschließend sein untergeordnetes Element innerhalb des Rahmens. EINTextElement
zeichnet nur Text. Wenn Sie nun eine Schaltfläche möchten, können Sie eine erstellen, indem Sie diese beiden Grundelemente zusammensetzen, dh Text in den Rahmen einfügen. Hier ist die Grenze so etwas wie ein Dekorateur.Der Beitrag wird ziemlich lang, also lass es mich wissen, wenn du diese Idee interessant findest und ich kann ein paar Beispiele oder mehr Erklärungen geben.
quelle
Schneiden Sie den Code aus und gehen Sie zum Kern Ihrer Frage "Ist mein Design mit abstrakten Basisklassen und Interfaces nicht als Typen, sondern eher als Verhalten ein gutes Design oder nicht", würde ich sagen, dass an diesem Ansatz nichts auszusetzen ist.
Ich habe in der Tat einen solchen Ansatz (in meiner Rendering-Engine) verwendet, bei dem jede Schnittstelle das Verhalten definiert, das das verbrauchende Subsystem erwartet. Zum Beispiel IUpdateable, ICollidable, IRenderable, ILoadable, ILoader usw. Aus Gründen der Klarheit habe ich auch jedes dieser "Verhaltensweisen" in separate Teilklassen wie "Entity.IUpdateable.cs", "Entity.IRenderable.cs" unterteilt und versucht, die Felder und Methoden so unabhängig wie möglich zu halten.
Der Ansatz, Interfaces zur Definition von Verhaltensmustern zu verwenden, stimmt ebenfalls mit mir überein, da Generics, Constraints und Parameter vom Typ co (ntra) variant sehr gut zusammenarbeiten.
Eines der Dinge, die ich bei dieser Entwurfsmethode beobachtet habe, war: Wenn Sie jemals feststellen, dass Sie eine Schnittstelle mit mehr als einer Handvoll Methoden definieren, implementieren Sie wahrscheinlich kein Verhalten.
quelle