Мне нужно спроектировать иерархию классов для моего проекта на C #. По сути, функциональные возможности классов аналогичны классам WinForms, поэтому давайте возьмем в качестве примера инструментарий WinForms. (Однако я не могу использовать WinForms или WPF.)
Есть некоторые основные свойства и функциональные возможности, которые должен обеспечить каждый класс. Размеры, положение, цвет, видимость (true / false), метод Draw и т. Д.
Мне нужен совет по дизайну. Я использовал дизайн с абстрактным базовым классом и интерфейсами, которые на самом деле не являются типами, а больше похожи на поведение. Это хороший дизайн? Если нет, то что будет лучше дизайн.
Код выглядит так:
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();
}
Некоторые элементы управления могут содержать другие элементы управления, некоторые могут содержаться только (как дочерние элементы), поэтому я подумываю создать два интерфейса для этих функций:
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 контролирует отображение текста, так что это также входит в интерфейс:
interface ITextHolder
{
string Text { get; set; }
int TextPositionX { get; set; }
int TextPositionY { get; set; }
int TextWidth { get; }
int TextHeight { get; }
void DrawText();
}
Некоторые элементы управления могут быть закреплены внутри родительского элемента управления так:
enum Docking
{
None, Left, Right, Top, Bottom, Fill
}
interface IDockable
{
Docking Dock { get; set; }
}
... а теперь давайте создадим несколько конкретных классов:
class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}
Первая «проблема», о которой я могу подумать, заключается в том, что интерфейсы в основном устанавливаются в камне после их публикации. Но давайте предположим, что я смогу сделать свои интерфейсы достаточно хорошими, чтобы избежать необходимости вносить в них изменения в будущем.
Другая проблема, которую я вижу в этом проекте, заключается в том, что каждый из этих классов должен будет реализовывать свои интерфейсы, и дублирование кода будет происходить быстро. Например, в метках и кнопках метод DrawText () происходит из интерфейса ITextHolder или в каждом классе, полученном из управления дочерними элементами IContainer.
Мое решение этой проблемы состоит в том, чтобы реализовать эти «дублированные» функции в выделенных адаптерах и переадресовывать вызовы к ним. Таким образом, и Label, и Button будут иметь элемент TextHolderAdapter, который будет вызываться внутри методов, унаследованных от интерфейса ITextHolder.
Я думаю, что этот дизайн должен оградить меня от необходимости иметь много общих функций в базовом классе, которые могут быстро раздуться с помощью виртуальных методов и ненужного «шумового кода». Изменения в поведении будут реализованы за счет расширения адаптеров, а не производных от Control классов.
Я думаю, что это называется «Стратегия», и, хотя на эту тему существуют миллионы вопросов и ответов, я хотел бы спросить вас о вашем мнении относительно того, что я принимаю во внимание для этого дизайна, и какие недостатки вы можете вспомнить в мой подход.
Я должен добавить, что есть почти 100% вероятность того, что будущие требования потребуют новых классов и новых функциональных возможностей.
источник
System.ComponentModel.Component
или ,System.Windows.Forms.Control
или любой из других существующих базовых классов? Зачем вам нужно создавать собственную иерархию управления и заново определять все эти функции с нуля?IChild
кажется ужасным именем.Ответы:
[1] Добавьте виртуальные «геттеры» и «сеттеры» к своим свойствам, мне пришлось взломать другую библиотеку элементов управления, потому что мне нужна эта функция:
Я знаю, что это предложение является более "многословным" или сложным, но оно очень полезно в реальном мире.
[2] Добавьте свойство «IsEnabled», не путайте с «IsReadOnly»:
Означает, что вам, возможно, придется показать свой контроль, но не показывать никакой информации.
[3] Добавьте свойство «IsReadOnly», не путайте с «IsEnabled»:
Это означает, что элемент управления может отображать информацию, но не может быть изменен пользователем.
источник
Хороший вопрос! Первое, что я заметил, это то, что метод draw не имеет параметров, где он рисует? Я думаю, что он должен получить некоторый объект Surface или Graphics в качестве параметра (как, конечно, интерфейс).
Еще одна вещь, которую я нахожу странным, - это интерфейс IContainer. У него есть
GetChild
метод, который возвращает один дочерний элемент и не имеет параметров - может быть, он должен вернуть коллекцию или, если вы переместите метод Draw в интерфейс, он может реализовать этот интерфейс и иметь метод draw, который может рисовать свою внутреннюю дочернюю коллекцию, не подвергая ее ,Может быть, для идей вы могли бы взглянуть и на WPF - это, на мой взгляд, очень хорошо продуманный фреймворк. Также вы можете взглянуть на шаблоны дизайна Composition и Decorator. Первый может быть полезен для элементов контейнера, а второй - для добавления функциональности к элементам. Я не могу сказать, насколько хорошо это будет работать на первый взгляд, но вот что я думаю:
Чтобы избежать дублирования, у вас может быть что-то вроде примитивных элементов - например, текстового элемента. Затем вы можете создавать более сложные элементы, используя эти примитивы. Например, a
Border
является элементом, у которого есть единственный дочерний элемент. Когда вы вызываете егоDraw
метод, он рисует границу и фон, а затем рисует своего потомка внутри границы. АTextElement
только рисует какой-то текст. Теперь, если вам нужна кнопка, вы можете создать ее, составив эти два примитива, то есть поместить текст в рамку. Здесь Граница - это что-то вроде декоратора.Пост становится довольно длинным, поэтому дайте мне знать, если вы найдете эту идею интересной, и я могу привести несколько примеров или больше объяснений.
источник
Вырезав код и перейдя к сути вашего вопроса «Является ли мой дизайн абстрактным базовым классом и интерфейсами не как тип, а скорее как поведение, это хороший дизайн или нет», я бы сказал, что в этом подходе нет ничего плохого.
На самом деле я использовал такой подход (в моем механизме рендеринга), где каждый интерфейс определял поведение ожидаемой подсистемы потребления. Например, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader и так далее и так далее. Для ясности я также разделил каждое из этих «поведений» на отдельные частичные классы, такие как «Entity.IUpdateable.cs», «Entity.IRenderable.cs», и попытался сохранить поля и методы как можно более независимыми.
Подход с использованием интерфейсов для определения поведенческих паттернов также согласуется со мной, потому что обобщенные параметры, ограничения и параметры варианта типа (ntra) работают очень хорошо вместе.
Одна из вещей, которые я заметил об этом методе проектирования, была: если вы когда-нибудь определяете интерфейс с помощью нескольких методов, вы, вероятно, не реализуете поведение.
источник