Necesito diseñar una jerarquía de clases para mi proyecto C #. Básicamente, las funcionalidades de la clase son similares a las clases de WinForms, así que tomemos el kit de herramientas de WinForms como ejemplo. (Sin embargo, no puedo usar WinForms o WPF).
Hay algunas propiedades y funcionalidades centrales que cada clase necesita proporcionar. Dimensiones, posición, color, visibilidad (verdadero / falso), método de dibujo, etc.
Necesito consejos de diseño. He utilizado un diseño con una clase base abstracta e interfaces que no son realmente tipos, sino más bien comportamientos. ¿Es este un buen diseño? Si no, cuál sería un mejor diseño.
El código se ve así:
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();
}
Algunos controles pueden contener otros controles, algunos solo pueden estar contenidos (como hijos), así que estoy pensando en hacer dos interfaces para estas funcionalidades:
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 controla el texto de la pantalla, por lo que esto también entra en la interfaz:
interface ITextHolder
{
string Text { get; set; }
int TextPositionX { get; set; }
int TextPositionY { get; set; }
int TextWidth { get; }
int TextHeight { get; }
void DrawText();
}
Algunos controles se pueden acoplar dentro de su control principal para que:
enum Docking
{
None, Left, Right, Top, Bottom, Fill
}
interface IDockable
{
Docking Dock { get; set; }
}
... y ahora creemos algunas clases concretas:
class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}
El primer "problema" en el que puedo pensar aquí es que las interfaces se establecen básicamente en piedra una vez que se publican. Pero supongamos que podré hacer que mis interfaces sean lo suficientemente buenas para evitar la necesidad de hacer cambios en ellas en el futuro.
Otro problema que veo en este diseño es que cada una de estas clases necesitaría implementar sus interfaces y la duplicación de código ocurrirá rápidamente. Por ejemplo, en Label and Button, el método DrawText () se deriva de la interfaz ITextHolder o de cada clase derivada de la administración de IContainer de niños.
Mi solución para este problema es implementar estas funcionalidades "duplicadas" en adaptadores dedicados y reenviar llamadas a ellos. Por lo tanto, Label y Button tendrían un miembro TextHolderAdapter que se llamaría dentro de los métodos heredados de la interfaz ITextHolder.
Creo que este diseño debería protegerme de tener muchas funcionalidades comunes en la clase base que podrían hincharse rápidamente con métodos virtuales y un "código de ruido" innecesario. Los cambios de comportamiento se realizarían ampliando los adaptadores y no las clases derivadas de Control.
Creo que esto se llama el patrón "Estrategia" y, aunque hay millones de preguntas y respuestas sobre ese tema, me gustaría pedirle su opinión sobre lo que tengo en cuenta para este diseño y qué defectos puede pensar en mi acercamiento.
Debo agregar que hay casi un 100% de posibilidades de que los requisitos futuros requieran nuevas clases y nuevas funcionalidades.
fuente
System.ComponentModel.Component
o deSystem.Windows.Forms.Control
cualquiera de las otras clases base existentes? ¿Por qué necesita crear su propia jerarquía de control y definir todas estas funciones nuevamente desde cero?IChild
Parece un nombre horrible.Respuestas:
[1] Agregue "getters" y "setters" virtuales a sus propiedades, tuve que hackear otra biblioteca de control, porque necesito esta función:
Sé que esta sugerencia es más "detallada" o compleja, pero es muy útil en el mundo real.
[2] Agregue una propiedad "IsEnabled", no confunda con "IsReadOnly":
Significa que puede que tenga que mostrar su control, pero no muestra ninguna información.
[3] Agregue una propiedad "IsReadOnly", no confunda con "IsEnabled":
Eso significa que el control puede mostrar información, pero el usuario no puede cambiarlo.
fuente
¡Buena pregunta! Lo primero que noto es que el método de dibujo no tiene ningún parámetro, ¿dónde dibuja? Creo que debería recibir algún objeto Surface o Graphics como parámetro (como interfaz, por supuesto).
Una cosa más que encuentro extraño es la interfaz IContainer. Tiene
GetChild
método que devuelve un solo hijo y no tiene parámetros; tal vez debería devolver una colección o si mueve el método Draw a una interfaz, podría implementar esta interfaz y tener un método de dibujo que pueda dibujar su colección infantil interna sin exponerla .Tal vez para ideas también podría mirar WPF: en mi opinión, es un marco muy bien diseñado. También puede echar un vistazo a los patrones de diseño de Composición y Decorador. El primero puede ser útil para elementos de contenedor y el segundo para agregar funcionalidad a los elementos. No puedo decir qué tan bien funcionará a primera vista, pero esto es lo que pienso:
Para evitar la duplicación, podría tener algo como elementos primitivos, como el elemento de texto. Entonces puedes construir los elementos más complicados usando estas primitivas. Por ejemplo, a
Border
es un elemento que tiene un solo hijo. Cuando llamas a suDraw
método, dibuja un borde y un fondo y luego dibuja a su hijo dentro del borde. ATextElement
solo dibuja algo de texto. Ahora, si desea un botón, puede crear uno componiendo esas dos primitivas, es decir, poner texto dentro del borde. Aquí la frontera es algo así como un decorador.La publicación se está haciendo bastante larga, así que avíseme si encuentra esa idea interesante y puedo dar algunos ejemplos o más explicaciones.
fuente
Recortando el código y yendo al núcleo de su pregunta "¿Mi diseño con clase base abstracta e interfaces no son como tipos sino más bien como comportamientos es un buen diseño o no?", Diría que no hay nada de malo en ese enfoque.
De hecho, he utilizado este enfoque (en mi motor de renderizado) donde cada interfaz definió el comportamiento que el subsistema consumidor esperaba. Por ejemplo, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader y así sucesivamente. Para mayor claridad, también separé cada uno de estos "comportamientos" en clases parciales separadas como "Entity.IUpdateable.cs", "Entity.IRenderable.cs" e intenté mantener los campos y métodos lo más independientes posible.
El enfoque de usar interfaces para definir patrones de comportamiento también concuerda conmigo porque los parámetros genéricos, las restricciones y los tipos de variante co (ntra) funcionan muy bien juntos.
Una de las cosas que observé sobre este método de diseño fue: si alguna vez te encuentras definiendo una interfaz con más de un puñado de métodos, probablemente no estés implementando un comportamiento.
fuente