Но разве метод draw () не сильно зависит от пользовательского интерфейса?
С прагматической точки зрения, некоторый код в вашей системе должен знать, как нарисовать что-то вроде Rectangle
если это требование конечного пользователя. И в какой-то момент все сводится к выполнению действительно низкоуровневых вещей, таких как растеризация пикселей или отображение чего-либо в консоли.
Вопрос для меня с точки зрения связи: кто / что должен зависеть от этого типа информации и от какой степени детализации (например, абстрактной)?
Абстрагирование возможностей рисования / рендеринга
Потому что, если код рисования более высокого уровня зависит только от чего-то очень абстрактного, эта абстракция может работать (путем замены конкретных реализаций) на всех платформах, на которые вы собираетесь ориентироваться. В качестве надуманного примера, некоторые очень абстрактныеIDrawer
интерфейсы могут быть реализованы как в консоли, так и в GUI API, чтобы выполнять такие вещи, как формы графиков (реализация консоли может обрабатывать консоль как некое «изображение» 80xN с использованием ASCII-графики). Конечно, это надуманный пример, поскольку обычно это не то, что вам нужно, это обрабатывать вывод консоли как буфер изображений / кадров; как правило, большинству пользователей требуется больше текстовых взаимодействий в консолях.
Еще один вопрос: насколько легко создать стабильную абстракцию? Потому что это может быть легко, если все, на что вы нацеливаетесь, - это современные API-интерфейсы GUI для абстрагирования основных возможностей рисования фигур, таких как построение линий, прямоугольников, путей, текста и тому подобное (просто простая растеризация 2D ограниченного набора примитивов) с одним абстрактным интерфейсом, который может быть легко реализован для них через различные подтипы с минимальными затратами. Если вы можете эффективно спроектировать такую абстракцию и реализовать ее на всех целевых платформах, то я бы сказал, что это гораздо меньшее зло, если даже вообще не зло, для формы или элемента управления графическим интерфейсом или что-то еще, чтобы знать, как рисовать себя, используя такую абстракция.
Но, скажем, вы пытаетесь абстрагироваться от мрачных деталей, которые различаются между Playstation Portable, iPhone, XBox One и мощным игровым ПК, в то время как вы должны использовать самые передовые технологии 3D-рендеринга / затенения в реальном времени на каждом из них. , В этом случае попытка придумать один абстрактный интерфейс для абстрагирования деталей рендеринга, когда базовые аппаратные возможности и API-интерфейсы настолько дико изменяются, почти наверняка приведет к огромному времени на разработку и перепроектирование, высокую вероятность повторяющихся изменений дизайна с непредвиденными открытия, а также решение с наименьшим общим знаменателем, которое не в состоянии использовать полную уникальность и мощь базового оборудования.
Обеспечение зависимости от стабильных, «легких» конструкций
В моей области я в этом последнем сценарии. Мы нацелены на множество разного оборудования с принципиально разными базовыми возможностями и API, и попытаться придумать одну абстракцию рендеринга / рисования, чтобы управлять ими всеми, безгранично безнадежно (мы можем стать всемирно известными, просто делая это эффективно, как в игре сменщик в отрасли). Поэтому последнее , что я хочу в моем случае это как аналоговая Shape
или Model
или Particle Emitter
что умеет рисовать себя, даже если он выражает , что рисунок на самом высоком уровне и , возможно , наиболее абстрактный способ ...
... потому что эти абстракции слишком сложны для правильного проектирования, и когда дизайн трудно исправить, и все зависит от него, это рецепт для самых дорогостоящих центральных изменений дизайна, которые пульсируют и ломают все в зависимости от этого. Поэтому последнее, что вам нужно, - это чтобы зависимости в ваших системах направлялись к абстрактным проектам, слишком трудно их исправить (слишком сложно стабилизировать без навязчивых изменений).
Сложный Зависит от Легко, Не Легко Зависит от Трудно
Вместо этого мы делаем так, чтобы зависимости перетекли в вещи, которые легко спроектировать. Гораздо проще спроектировать абстрактную «Модель», которая просто сфокусирована на хранении таких вещей, как многоугольники и материалы, и придать ей правильную конструкцию, чем спроектировать абстрактный «Рендерер», который можно эффективно реализовать (с помощью заменяемых конкретных подтипов) для обслуживания чертежей единообразно запрашивает аппаратное обеспечение, такое же, как PSP от ПК.
Таким образом, мы инвертируем зависимости от вещей, которые сложно спроектировать. Вместо того, чтобы заставлять абстрактные модели знать, как рисовать себя в дизайне абстрактного рендерера, от которого они все зависят (и нарушать свои реализации, если этот дизайн меняется), у нас вместо этого есть абстрактный рендерер, который знает, как рисовать каждый абстрактный объект в нашей сцене ( модели, излучатели частиц и т. д.), и поэтому мы можем затем реализовать подтип рендерера OpenGL для ПК, например RendererGl
, другой для PSP RendererPsp
, другой для мобильных телефонов и т. д. В этом случае зависимости стремятся к стабильным проектам, их легко получить правильно, от рендера до различных типов объектов (моделей, частиц, текстур и т. д.) в нашей сцене, а не наоборот.
- Я использую «стабильность / нестабильность» в несколько ином смысле, чем метрика афферентных / эфферентных связей дяди Боба, которая, насколько я понимаю, больше измеряет трудность изменений Я говорю больше о «вероятности необходимости изменений», хотя его метрика стабильности там полезна. Когда «вероятность изменения» пропорциональна «легкости изменения» (например, вещи, которые, скорее всего, потребуют изменений, имеют самую высокую нестабильность и афферентные связи из метрики дяди Боба), тогда любые такие вероятные изменения дешевы и не требуют вмешательства , требующий только замены реализации, не касаясь каких-либо центральных конструкций.
Если вы нашли себя, пытаясь абстрагироваться от чего-то на центральном уровне вашего кодовую и это слишком сложно разработать, вместо того, чтобы упорно биться головой о стены и постоянно делает навязчивые изменения к нему каждый месяц / год, что требует обновления 8000 исходных файлов, так как это ломая все, что от него зависит, мое первое предложение - рассмотреть возможность обращения зависимостей. Посмотрите, сможете ли вы написать код таким образом, чтобы вещь, которую так сложно спроектировать, зависела от всего, что легче спроектировать, не имея вещей, которые проще спроектировать, в зависимости от того, что так сложно спроектировать. Обратите внимание, что я имею в виду проекты (в частности, дизайн интерфейсов), а не реализации: иногда вещи легко проектировать и сложно реализовать, а иногда вещи сложно спроектировать, но легко реализовать. Зависимости перетекают к проектам, поэтому следует сосредоточиться только на том, насколько сложно что-то спроектировать здесь, чтобы определить направление, в котором текут зависимости.
Принцип единой ответственности
Для меня SRP здесь обычно не так интересен (хотя в зависимости от контекста). Я имею в виду, что при проектировании вещей, которые ясны по своему назначению и поддержанию, есть жесткая балансировка, но вашим Shape
объектам, возможно, придется предоставить более подробную информацию, если, например, они не знают, как рисовать себя, и может быть не так много значимых вещей для сделать с формой в конкретном контексте использования, чем построить его и нарисовать его. Есть компромиссы практически со всем, и это не связано с SRP, который может заставить вещи осознать, как сделать себя способным стать таким кошмаром обслуживания в моем опыте в определенных контекстах.
Это гораздо больше связано со связью и направлением распространения зависимостей в вашей системе. Если вы пытаетесь портировать абстрактный интерфейс рендеринга, от которого все зависит (потому что они используют его для рисования самих себя), к новому целевому API / аппаратному обеспечению и понимаете, что вам нужно значительно изменить его дизайн, чтобы заставить его работать там эффективно, тогда это очень дорогостоящее изменение, которое требует замены реализаций всего в вашей системе, которая умеет рисовать себя. И это наиболее практичная проблема обслуживания, с которой я сталкиваюсь, когда люди осознают, как рисовать себя, если это приводит к множеству зависимостей, перетекающих в абстракции, которые слишком сложно правильно спроектировать заранее.
Гордость разработчика
Я упоминаю об этом, потому что, по моему опыту, это часто является самым большим препятствием на пути направления зависимостей к вещам, которые легче спроектировать. Разработчикам очень легко стать немного амбициозными и сказать: «Я собираюсь разработать кроссплатформенную абстракцию рендеринга, чтобы управлять ими всеми, я решу, что другие разработчики тратят месяцы на портирование, и я собираюсь получить это правильно, и это будет работать как волшебство на каждой платформе, которую мы поддерживаем, и использовать самые современные методы рендеринга на каждой; я уже думал об этом ».В этом случае они отказываются от практического решения, которое состоит в том, чтобы избежать этого и просто изменить направление зависимостей и перевести то, что может быть чрезвычайно дорогостоящим, и повторяющиеся центральные изменения проекта в просто дешевые и локальные повторяющиеся изменения в реализации. В разработчиках должен быть какой-то инстинкт «белого флага», чтобы сдаться, когда что-то слишком сложно спроектировать до такого абстрактного уровня, и пересмотреть всю свою стратегию, в противном случае их ждет много горя и боли. Я бы посоветовал перенести такие амбиции и боевой дух в современные реализации, которые легче спроектировать, чем перенести такие покоряющие мир амбиции на уровень дизайна интерфейса.