Интерфейс как возможность или интерфейс как тип

Предположим, что у меня есть такое требование:
Объекты в системе производятся из базового класса с именем IObject и могут иметь объекты с цветом, объекты с преобразованиями и т.д.

Теперь есть два подхода к разработке иерархии классов.
Первый из них:

просто позвольте конкретному классу, полученному из IObject, а также выберите "возможность", интерфейсов в качестве базового класса для указывают, что он поддерживает такое поведение, как интерфейс: IHasColor,
IHasTransformation

Второй:

Организуйте базовые классы, и пусть конкретные классы, полученные из одного из их: IObject, IColorObject, ITransfromationObject, IColorAndTransformationObject

Я предпочитаю первый (имеет ли формальное имя?), поскольку он более гибкий, и, как вы видите, у второго может возникнуть проблема с комбинацией классов, когда есть много атрибутов, таких как цвет, преобразование...

Я хотел бы знать ваши идеи и предложения.

Спасибо.

Ответ 1

Классы абстрагируют реальное понятие типов объектов.

Интерфейсы абстрагируют реальную концепцию поведения или способностей для объекта.

Таким образом, вопросы становятся, является ли "цвет" свойством объекта или является ли он способностью объекта?

Когда вы создаете иерархию, вы сдерживаете мир в более узком пространстве. Если вы примете цвет как свойство объекта, тогда у вас будет два типа объектов, те, которые имеют цвета, и те, которые этого не делают. Соответствует ли это вашему "миру"?

Если вы моделируете его как возможность (Интерфейс), тогда у вас будут объекты, которые могут предоставить, скажем, отбрасывание, цвет в мир.

Для преобразования применяется одна и та же логика. Вы можете либо разделить мир на два типа объектов, те, которые могут трансформироваться, а те, кто не может, или вы можете рассматривать его как способность, объект может иметь возможность трансформироваться в другое.

Для меня, с этой точки зрения, было бы разумно:

  • Цвет является свойством объекта. На самом деле каждый объект должен иметь цвет, даже если его прозрачность, даже если это отражение, даже если его "нет" (удачи, выясняя, что объект с цветом = нет, означает в реальном мире, все же это может иметь смысл в вашем программная логика).
  • Трансформация - это способность, то есть интерфейс, что-то, что объект способен делать, среди прочего, объект может или не сможет выполнить.

Ответ 2

Я думаю, вы прыгаете прямо в интерфейсы, пропуская классы. Требуется для вас приложение. иметь интерфейс "IObject"? Возможно, корневой класс "CObject" для вашей иерархии классов может вам помочь.

Он считает, что победителем является решение № 1, у вас может быть "MyObject", будь то реализация интерфейса или прямой класс. Позже вы можете добавить дополнительные классы или интерфейсы в свою иерархию классов, как вам нужно.

После просмотра нескольких приложений (некоторые мои, некоторые другие), я думаю, что должен существовать "шаблонный иерархический корневой объект класса пользовательского приложения" или "шаблон шаблона иерархической иерархии пользовательского приложения".

Ответ 3

Я работаю над иерархией классов в своем проекте, и в основном у меня такая же ситуация, как вы описали в своем вопросе.

Скажем, у меня есть базовый тип Object, который является абсолютным корнем всех других классов моего инструментария. Поэтому, естественно, все происходит от него напрямую или через него подклассами. Существует общая функциональность, которую должен выполнять каждый класс, созданный объектами, но в некоторых свойствах классов листьев мало чем отличаются от других. Например, каждый объект имеет размер и позицию, которые могут быть изменены с помощью свойств или методов, таких как Position = new Point (10, 10), Width = 15 и т.д. Но есть классы, которые должны игнорировать настройку свойства или изменять его в соответствии с самим собой внутреннее состояние. Подумайте о контроле, прикрепленном к левой стороне родительского окна. Вы можете установить свойство Height все, что вам нравится, но оно будет вообще игнорироваться, потому что это свойство действительно зависит от высоты родительского контроля контейнера (или это высота ClientArea или sth).

Таким образом, если абстрактный класс Object, реализующий основные общие функции, подходит, пока вы не достигнете точки, где вам нужно "настроить" поведение. Если Object предоставляет защищенный виртуальный метод SetHeight, который вызывается в настройке свойства Height, вы можете переопределить его в классе DockedControl и разрешить изменение высоты только в том случае, если стыковка является None, в других случаях вы ограничиваете ее или игнорируете полностью.

Итак, мы счастливы, но теперь у нас есть требование для объекта, который реагирует на события мыши, такие как Click или Hover. Итак, мы выводим MouseAwareObject из абстрактного класса Object и реализуем события и прочее.

И теперь клиент хочет подключаемые объекты мыши. Итак, мы получаем DockableObject и... хм, что теперь? Если мы сможем сделать многократное наследование, мы можем это сделать, но мы сталкиваемся с проблемой алмаза с двусмысленностью дублированного интерфейса, и нам нужно иметь дело с этим. У нас может быть два члена типа Dockable и MouseAware в новых внешних и внешних вызовах для обеспечения функциональности.

И последнее, что приходит на ум, - создать интерфейсы IDockable и IMouseAware и дать им возможность определять функциональность и добавлять их свободно только к объектам, которые должны доставлять конкретные поведения/реализации.

Я думаю, что я разделил свой базовый класс на части и оставил свой Object с очень ограниченным "ядром" набором свойств и методов и остальной функциональностью, которая на самом деле является необязательной для объектов как типа, но в конкретных случаях переходите к интерфейсам как IResizable, IDockable, IMakeAWorldABetterPlaceAble и т.д. С помощью этого решения можно "присоединить" поведение к классам, производным от объектов, без необходимости использовать виртуальные или абстрактные виртуальные (абстрактные) методы draggin из корневого базового класса вплоть до класса листа.

Конечно, неудобства реализации интерфейсов во всех затронутых классах, но вы всегда можете реализовать некоторые "адаптеры" и просто переадресовать их. Таким образом, у вас нет дублированной реализации (в некоторой степени, конечно) и развязки между реализацией задачи (Resize может означать разные вещи для разных классов) и ожидания кода клиента.

Возможно, это не идеальный ответ на ваш вопрос, но, возможно, он подскажет вам свое решение.