J'ai besoin de concevoir une hiérarchie de classes pour mon projet C #. Fondamentalement, les fonctionnalités des classes sont similaires aux classes WinForms, prenons donc la boîte à outils WinForms comme exemple. (Cependant, je ne peux pas utiliser WinForms ou WPF.)
Il y a quelques propriétés et fonctionnalités de base que chaque classe doit fournir. Dimensions, position, couleur, visibilité (vrai / faux), méthode de dessin, etc.
J'ai besoin de conseils de conception J'ai utilisé une conception avec une classe de base abstraite et des interfaces qui ne sont pas vraiment des types mais plutôt des comportements. Est-ce un bon design? Sinon, quelle serait une meilleure conception.
Le code ressemble à ceci:
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();
}
Certains contrôles peuvent contenir d'autres contrôles, certains ne peuvent être contenus (qu'en tant qu'enfants), donc je pense à faire deux interfaces pour ces fonctionnalités:
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);
}
Les contrôles WinForms affichent du texte, donc cela va aussi dans l'interface:
interface ITextHolder
{
string Text { get; set; }
int TextPositionX { get; set; }
int TextPositionY { get; set; }
int TextWidth { get; }
int TextHeight { get; }
void DrawText();
}
Certains contrôles peuvent être ancrés dans leur contrôle parent afin:
enum Docking
{
None, Left, Right, Top, Bottom, Fill
}
interface IDockable
{
Docking Dock { get; set; }
}
... et maintenant créons des classes concrètes:
class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}
Le premier "problème" auquel je peux penser ici est que les interfaces sont fondamentalement figées une fois publiées. Mais supposons que je serai en mesure de rendre mes interfaces suffisamment bonnes pour éviter d'avoir à les modifier à l'avenir.
Un autre problème que je vois dans cette conception est que chacune de ces classes aurait besoin d'implémenter ses interfaces et la duplication de code se produira rapidement. Par exemple, dans Label et Button, la méthode DrawText () dérive de l'interface ITextHolder ou de chaque classe dérivée de la gestion IContainer des enfants.
Ma solution à ce problème consiste à implémenter ces fonctionnalités "dupliquées" dans des adaptateurs dédiés et à leur renvoyer les appels. Ainsi, Label et Button auraient tous deux un membre TextHolderAdapter qui serait appelé à l'intérieur de méthodes héritées de l'interface ITextHolder.
Je pense que cette conception devrait me protéger d'avoir de nombreuses fonctionnalités communes dans la classe de base qui pourraient rapidement être gonflées avec des méthodes virtuelles et un "code de bruit" inutile. Les changements de comportement seraient accomplis en étendant les adaptateurs et non les classes dérivées de Control.
Je pense que cela s'appelle le modèle de "stratégie" et bien qu'il y ait des millions de questions et réponses sur ce sujet, je voudrais vous demander votre avis sur ce que je prends en considération pour cette conception et sur les défauts auxquels vous pouvez penser mon approche.
Je dois ajouter qu'il y a presque 100% de chances que les exigences futures appellent de nouvelles classes et de nouvelles fonctionnalités.
la source
System.ComponentModel.Component
ouSystem.Windows.Forms.Control
de l'une des autres classes de base existantes? Pourquoi devez-vous créer votre propre hiérarchie de contrôle et définir à nouveau toutes ces fonctions à partir de zéro?IChild
semble être un nom horrible.Réponses:
[1] Ajoutez des "getters" et "setters" virtuels à vos propriétés, j'ai dû pirater une autre bibliothèque de contrôle, car j'ai besoin de cette fonctionnalité:
Je sais que cette suggestion est plus "verbeuse" ou complexe, mais elle est très utile dans le monde réel.
[2] Ajoutez une propriété "IsEnabled", ne confondez pas avec "IsReadOnly":
Cela signifie que vous devrez peut-être montrer votre contrôle, mais ne montrez aucune information.
[3] Ajoutez une propriété "IsReadOnly", ne confondez pas avec "IsEnabled":
Cela signifie que le contrôle peut afficher des informations, mais ne peut pas être modifié par l'utilisateur.
la source
Bonne question! La première chose que je remarque, c'est que la méthode draw n'a pas de paramètres, où dessine-t-elle? Je pense qu'il devrait recevoir un objet Surface ou Graphics comme paramètre (comme interface bien sûr).
Une autre chose que je trouve étrange est l'interface IContainer. Il a une
GetChild
méthode qui retourne un seul enfant et n'a aucun paramètre - il devrait peut-être renvoyer une collection ou si vous déplacez la méthode Draw vers une interface, il pourrait alors implémenter cette interface et avoir une méthode draw qui peut dessiner sa collection enfants interne sans l'exposer .Peut-être que pour des idées, vous pouvez également regarder WPF - c'est un cadre très bien conçu à mon avis. Vous pouvez également jeter un œil aux modèles de conception Composition et Décorateur. Le premier peut être utile pour les éléments de conteneur et le second pour ajouter des fonctionnalités aux éléments. Je ne peux pas dire à quel point cela fonctionnera à première vue, mais voici ce que je pense:
Pour éviter la duplication, vous pourriez avoir quelque chose comme des éléments primitifs - comme l'élément texte. Ensuite, vous pouvez construire les éléments les plus compliqués en utilisant ces primitives. Par exemple, a
Border
est un élément qui a un seul enfant. Lorsque vous appelez saDraw
méthode, il dessine une bordure et un arrière-plan, puis dessine son enfant à l'intérieur de la bordure. ATextElement
ne dessine que du texte. Maintenant, si vous voulez un bouton, vous pouvez en créer un en composant ces deux primitives, c'est-à-dire en mettant du texte à l'intérieur de la bordure. Ici, la frontière est quelque chose comme un décorateur.Le message devient assez long, alors faites-moi savoir si vous trouvez cette idée intéressante et je peux donner quelques exemples ou plus d'explications.
la source
Découper le code et aller au cœur de votre question "Ma conception avec une classe de base abstraite et des interfaces n'est-elle pas en tant que types mais plutôt en tant que comportements est-elle une bonne conception ou non?", Je dirais qu'il n'y a rien de mal à cette approche.
J'ai en fait utilisé une telle approche (dans mon moteur de rendu) où chaque interface définissait le comportement attendu du sous-système consommateur. Par exemple, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader et ainsi de suite, etc. Pour plus de clarté, j'ai également séparé chacun de ces "comportements" en classes partielles distinctes comme "Entity.IUpdateable.cs", "Entity.IRenderable.cs" et j'ai essayé de garder les champs et les méthodes aussi indépendants que possible.
L'approche d'utiliser des interfaces pour définir des modèles de comportement est également d'accord avec moi car les génériques, les contraintes et les paramètres de type variant co (ntra) fonctionnent très bien ensemble.
L'une des choses que j'ai observées à propos de cette méthode de conception était: si jamais vous vous retrouvez à définir une interface avec plus d'une poignée de méthodes, vous n'implémentez probablement pas de comportement.
la source