Eu preciso criar uma hierarquia de classes para o meu projeto c #. Basicamente, as funcionalidades da classe são semelhantes às classes WinForms, portanto, tomemos o kit de ferramentas WinForms como exemplo. (No entanto, não posso usar o WinForms ou o WPF.)
Existem algumas propriedades e funcionalidades essenciais que toda classe precisa fornecer. Dimensões, posição, cor, visibilidade (verdadeiro / falso), método Draw, etc.
Preciso de conselhos de design. Utilizei um design com uma classe base abstrata e interfaces que não são realmente tipos, mas sim comportamentos. Esse é um bom design? Caso contrário, qual seria um design melhor.
O código fica assim:
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();
}
Alguns controles podem conter outros controles, alguns podem ser contidos apenas (como filhos), por isso estou pensando em fazer duas interfaces para essas 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);
}
Os controles WinForms exibem o texto, então isso também entra na interface:
interface ITextHolder
{
string Text { get; set; }
int TextPositionX { get; set; }
int TextPositionY { get; set; }
int TextWidth { get; }
int TextHeight { get; }
void DrawText();
}
Alguns controles podem ser encaixados dentro do controle pai, portanto:
enum Docking
{
None, Left, Right, Top, Bottom, Fill
}
interface IDockable
{
Docking Dock { get; set; }
}
... e agora vamos criar algumas classes concretas:
class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}
O primeiro "problema" em que posso pensar aqui é que as interfaces são basicamente definidas quando são publicadas. Mas vamos supor que poderei tornar minhas interfaces boas o suficiente para evitar a necessidade de fazer alterações nelas no futuro.
Outro problema que vejo neste design é que todas essas classes precisariam implementar suas interfaces e a duplicação de código ocorrerá rapidamente. Por exemplo, em Label e Button, o método DrawText () deriva da interface ITextHolder ou em todas as classes derivadas do gerenciamento de filhos do IContainer.
Minha solução para esse problema é implementar essas funcionalidades "duplicadas" em adaptadores dedicados e encaminhar chamadas para eles. Portanto, Label e Button teriam um membro TextHolderAdapter que seria chamado dentro de métodos herdados da interface ITextHolder.
Eu acho que esse design deve me impedir de ter muitas funcionalidades comuns na classe base, que podem rapidamente ficar inchadas com métodos virtuais e "código de ruído" desnecessário. Alterações de comportamento seriam realizadas estendendo adaptadores e não classes derivadas de controle.
Acho que isso é chamado de padrão "Estratégia" e, embora existam milhões de perguntas e respostas sobre esse tópico, gostaria de pedir suas opiniões sobre o que levo em consideração para esse design e quais falhas você pode encontrar em minha abordagem.
Devo acrescentar que há quase 100% de chance de que requisitos futuros exijam novas classes e novas funcionalidades.
fonte
System.ComponentModel.Component
ou deSystem.Windows.Forms.Control
qualquer outra classe base existente? Por que você precisa criar sua própria hierarquia de controle e definir todas essas funções novamente do zero?IChild
parece um nome horrível.Respostas:
[1] Adicione "getters" e "setters" virtuais às suas propriedades. Eu tive que invadir outra biblioteca de controle, porque preciso desse recurso:
Eu sei que essa sugestão é mais "detalhada" ou complexa, mas é muito útil no mundo real.
[2] Adicione uma propriedade "IsEnabled", não confunda com "IsReadOnly":
Significa que você pode ter que mostrar seu controle, mas não mostra nenhuma informação.
[3] Adicione uma propriedade "IsReadOnly", não confunda com "IsEnabled":
Isso significa que o controle pode exibir informações, mas não pode ser alterado pelo usuário.
fonte
Boa pergunta! A primeira coisa que noto é que o método draw não possui nenhum parâmetro, onde ele desenha? Eu acho que ele deve receber algum objeto Surface ou Graphics como parâmetro (como interface, é claro).
Mais uma coisa que acho estranha é a interface do IContainer. Ele possui um
GetChild
método que retorna um único filho e não possui parâmetros - talvez devesse retornar uma coleção ou, se você mover o método Draw para uma interface, ele poderá implementar essa interface e ter um método de desenho que possa desenhar sua coleção subordinada interna sem expô-la .Talvez para idéias que você possa ver também o WPF - na minha opinião, é um quadro muito bem projetado. Além disso, você pode dar uma olhada nos padrões de design Composition e Decorator. O primeiro pode ser útil para elementos de contêiner e o segundo para adicionar funcionalidade aos elementos. Não posso dizer o quão bem isso funcionará à primeira vista, mas eis o que penso:
Para evitar a duplicação, você pode ter algo como elementos primitivos - como o elemento de texto. Em seguida, você pode criar os elementos mais complicados usando essas primitivas. Por exemplo, a
Border
é um elemento que possui um único filho. Quando você chama seuDraw
método, ele desenha uma borda e um plano de fundo e depois desenha seu filho dentro da borda. ATextElement
apenas desenha algum texto. Agora, se você quiser um botão, poderá criar um compondo essas duas primitivas, ou seja, colocando o texto dentro da borda. Aqui a fronteira é algo como um decorador.O post está ficando bastante longo, então deixe-me saber se você acha essa ideia interessante e posso dar alguns exemplos ou mais explicações.
fonte
Cortando o código e indo para o centro da sua pergunta "O meu design com classe base abstrata e interfaces não é como tipos, mas mais como comportamentos é bom design ou não.", Eu diria que não há nada de errado com essa abordagem.
Na verdade, usei essa abordagem (no meu mecanismo de renderização) em que cada interface definia o comportamento esperado pelo subsistema consumidor. Por exemplo, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader e assim por diante. Para maior clareza, também separei cada um desses "comportamentos" em classes parciais separadas, como "Entity.IUpdateable.cs", "Entity.IRenderable.cs" e tentei manter os campos e métodos o mais independentes possível.
A abordagem do uso de interfaces para definir padrões comportamentais também concorda comigo, porque os parâmetros genéricos, de restrição e de tipo co (ntra) variante funcionam muito bem juntos.
Uma das coisas que observei sobre esse método de design foi: se você definir uma interface com mais de um punhado de métodos, provavelmente não estará implementando um comportamento.
fonte