Анотація базового класу з інтерфейсами як поведінка?


14

Мені потрібно розробити ієрархію класів для мого проекту C #. В основному, функції класу схожі на класи WinForms, тому давайте візьмемо інструментарій WinForms як приклад. (Однак я не можу використовувати WinForms або WPF.)

Є деякі основні властивості та функції, які повинен надати кожен клас. Розміри, положення, колір, видимість (справжня / хибна), метод малювання тощо.

Мені потрібні поради щодо дизайну. Я використав дизайн з абстрактним базовим класом та інтерфейсами, які насправді не типи, але більше схожі на поведінку. Це хороший дизайн? Якщо ні, то який би кращий дизайн.

Код виглядає так:

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 {}

Перша «проблема», яку я можу тут придумати, - це те, що інтерфейси в основному встановлюються в камені, як тільки вони публікуються. Але припустимо, що я зможу зробити свої інтерфейси досить хорошими, щоб уникнути необхідності вносити зміни до них у майбутньому.

Ще одна проблема, яку я бачу в цій конструкції, полягає в тому, що кожному з цих класів потрібно буде реалізувати свої інтерфейси, і дублювання коду швидко відбудеться. Наприклад, у Label and Button метод DrawText () походить від інтерфейсу ITextHolder або від кожного класу, що походить від управління дітьми IContainer.

Моє рішення цього питання полягає в реалізації цих «дублюваних» функцій у виділених адаптерах та переадресації дзвінків до них. Отже, і Label, і Button матимуть член TextHolderAdapter, який буде викликаний всередині методів, успадкованих від інтерфейсу ITextHolder.

Я думаю, що ця конструкція повинна захищати мене від безлічі загальних функціональних можливостей базового класу, які можуть швидко роздутись віртуальними методами та непотрібним "шумовим кодом". Зміни поведінки будуть досягнуті за допомогою розширення адаптерів, а не класів, що походять від контролю.

Я думаю, що це називається "стратегія", і хоча на цю тему є мільйони запитань і відповідей, я хотів би попросити ваших думок щодо того, що я беру до уваги під цим дизайном і які вади ви можете думати. мій підхід.

Додам, що є майже 100% шанс, що майбутні вимоги вимагатимуть нових класів та нових функціональних можливостей.


Чому б просто не успадкувати System.ComponentModel.Componentабо від System.Windows.Forms.Controlбудь-якого з інших існуючих базових класів? Чому вам потрібно створити власну ієрархію управління та знову визначити всі ці функції з нуля?
Коді Грей

3
IChildздається жахливим ім'ям.
Райнос

@Cody: по-перше, я не можу використовувати збірки WinForms або WPF, по-друге - давай, це лише приклад проблеми дизайну, про яку я питаю. Якщо вам легше, ви думаєте форми чи тварини. мої заняття повинні вести себе подібними до елементів керування WinForms, але не в усіх відношеннях і зовсім не так, як вони
grapkulec

2
Немає сенсу говорити, що ви не можете використовувати збірки WinForms або WPF, але ви зможете використовувати будь-яку власну систему управління, яку ви створили. Це серйозний випадок переосмислення колеса, і я не можу уявити, з якою можливою метою. Але якщо ви абсолютно зобов'язані, чому б не просто наслідувати приклад елементів управління WinForms? Це робить це питання досить застарілим.
Коді Грей

1
@graphkulec, тому це коментар :) Якби у мене були фактичні корисні відгуки, я б відповів на ваше запитання. Це все ще жахливе ім’я;)
Райнос

Відповіді:


5

[1] Щоб додати до своїх властивостей віртуальні "getters" та "setters", мені довелося зламати ще одну бібліотеку управління, оскільки мені потрібна ця функція:

abstract class Control
{
    // internal fields for properties
    protected int _Width;
    protected int _Height;
    protected int _BackColor;
    protected int _X;
    protected int _Y;
    protected bool Visible;

    protected int BorderWidth;
    protected int BorderColor;


    // getters & setters virtual !!!

    public virtual int getWidth(...) { ... }
    public virtual void setWidth(...) { ... }

    public virtual int getHeight(...) { ... }
    public virtual void setHeight(...) { ... }

    public virtual int getBackColor(...) { ... }
    public virtual void setBackColor(...) { ... }

    public virtual int getX(...) { ... }
    public virtual void setX(...) { ... }

    public virtual int getY(...) { ... }
    public virtual void setY(...) { ... }

    public virtual int getBorderWidth(...) { ... }
    public virtual void setBorderWidth(...) { ... }

    public virtual int getBorderColor(...) { ... }
    public virtual void setBorderColor(...) { ... }

    public virtual bool getVisible(...) { ... }
    public virtual void setVisible(...) { ... }

    // properties WITH virtual getters & setters

    public int Width { get getWidth(); set setWidth(value); }

    public int Height { get getHeight(); set setHeight(value); }

    public int BackColor { get getBackColor(); set setBackColor(value); }

    public int X { get getX(); set setX(value); }

    public int Y { get getY(); set setY(value); }

    public int BorderWidth { get getBorderWidth(); set setBorderWidth(value); }

    public int BorderColor { get getBorderColor(); set setBorderColor(value); }

    public bool Visible { get getVisible(); set setVisible(value); }

    // other methods

    public Rectangle ClipRectange { get; protected set; }   
    abstract public void Draw();
} // class Control

/* concrete */ class MyControl: Control
{
    public override bool getVisible(...) { ... }
    public override void setVisible(...) { ... }
} // class MyControl: Control

Я знаю, що ця пропозиція є більш "багатослівною" або складною, але дуже корисною в реальному світі.

[2] Додайте властивість "IsEnabled", не плутайте з "IsReadOnly":

abstract class Control
{
    // internal fields for properties
    protected bool _IsEnabled;

    public virtual bool getIsEnabled(...) { ... }
    public virtual void setIsEnabled(...) { ... }

    public bool IsEnabled{ get getIsEnabled(); set setIsEnabled(value); }
} // class Control

Значить, можливо, доведеться показати свій контроль, але не показувати інформацію.

[3] Додайте властивість "IsReadOnly", не плутайте з "IsEnabled":

abstract class Control
{
    // internal fields for properties
    protected bool _IsReadOnly;

    public virtual bool getIsReadOnly(...) { ... }
    public virtual void setIsReadOnly(...) { ... }

    public bool IsReadOnly{ get getIsReadOnly(); set setIsReadOnly(value); }
} // class Control

Це означає, що елемент керування може відображати інформацію, але користувач не може його змінити.


хоча мені не подобаються всі ці віртуальні методи в базовому класі, я дам їм другий шанс в міркуванні про дизайн. і ти нагадав мені, що мені дуже потрібна власність IsReadOnly :)
grapkulec

@grapkulec Оскільки він є базовим класом, ви повинні вказати, що похідні класи матимуть ці властивості, але, що методи, що керують поведінкою, можуть бути змінені для кожної групи класів ;-)
umlcat

mmkey, я бачу вашу думку і дякую вам, що
знайшли

1
Я прийняв це як відповідь, тому що, як тільки я почав реалізовувати свій «класний» дизайн з інтерфейсами, я швидко опинився у потребі віртуальних сетерів, а іноді й гетерів. Це не означає, що я взагалі не використовую інтерфейси, я просто обмежив їхнє використання місцями, де вони справді потрібні.
grapkulec

3

Приємне запитання! Перше, що я помічаю - це те, що метод малювання не має жодних параметрів, де він малюється? Я думаю, він повинен отримати якийсь об’єкт Surface або Graphics як параметр (як інтерфейс, звичайно).

Ще одне, що мені здається дивним - це інтерфейс IContainer. У ньому є GetChildметод, який повертає одиноку дитину і не має жодних параметрів - можливо, він повинен повернути колекцію або якщо ви перемістите метод Draw до інтерфейсу, то він може потім реалізувати цей інтерфейс і мати метод малювання, який може малювати його внутрішню дочірню колекцію, не виставляючи її .

Можливо, за ідеями можна було б також поглянути на WPF - це, на мою думку, дуже добре розроблена структура. Також ви можете поглянути на схеми дизайну композиції та декоратора. Перший може бути корисним для елементів контейнера, а другий для додавання функціональності елементам. Я не можу сказати, наскільки добре це буде працювати на перший погляд, але ось що я думаю:

Щоб уникнути дублювання, у вас може бути щось на зразок примітивних елементів - наприклад, текстовий елемент. Тоді ви можете побудувати більш складні елементи за допомогою цих примітивів. Наприклад, a Border- це елемент, у якого є одна дитина. Коли ви називаєте його Drawметод, він малює межу та фон, а потім малює свою дочку всередині кордону. А TextElementлише малює якийсь текст. Тепер, якщо вам потрібна кнопка, ви можете створити її, склавши ці два примітиви, тобто вставте текст всередині рамки. Тут Бордюр - це щось на зразок декоратора.

Пост стає досить довгим, тому повідомте мені, якщо вам ця ідея цікава, і я можу навести кілька прикладів чи більше пояснень.


1
відсутні парами - це не проблема, це лише ескіз реальних методів. Що стосується WPF, я створив систему компонування на основі їхньої ідеї, тому я думаю, що у мене вже складена частина композиції, а для декораторів мені доведеться подумати над цим. ваше уявлення про примітивні елементи звучить розумно, і я, безумовно, спробую. Спасибі!
grapkulec

@grapkulec Безкоштовно! Щодо парам - так, це нормально, я просто хотів бути впевненим, що я правильно розумію ваші наміри. Я радий, що ідея вам сподобалася. Удачі!

2

Вирізавши код і перейшовши до основи вашого питання "Чи є мій дизайн з абстрактним базовим класом та інтерфейсами не типовий, а більше, як поведінка - це гарний дизайн чи ні", я б сказав, що з цим підходом немає нічого поганого.

Насправді я застосував такий підхід (в моєму режимі візуалізації), коли кожен інтерфейс визначав поведінку, на яку очікувала споживана підсистема. Наприклад, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader тощо і так далі. Для наочності я також розділив кожну з цих "поведінок" на окремі часткові класи, такі як "Entity.IUpdateable.cs", "Entity.IRenderable.cs" і намагався зберегти поля та методи максимально незалежними.

Підхід використання інтерфейсів для визначення поведінкових моделей також погоджується зі мною, оскільки параметри типу generics, обмеження та co (ntra) варіанту дуже добре працюють разом.

Одне з речей, що я спостерігав щодо цього методу дизайну, було: Якщо ви коли-небудь опинитеся, що визначаєте інтерфейс з більш ніж кількома методами, ви, мабуть, не реалізуєте поведінку.


чи стикалися ви з якимись проблемами чи в якийсь момент ви шкодували про таку конструкцію і вам довелося боротися зі своїм кодом, щоб насправді виконати задумане? наприклад, наприклад, раптом виявилося, що вам потрібно створити так багато реалізацій поведінки, що це просто не мало сенсу, і ви хотіли, щоб ви просто дотримувались старої теми "зробіть базовий клас з мільйонами віртуалів і просто успадкуйте і переможете з нього пекло"?
grapkulec
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.