Какова лучшая практика, когда речь идет о написании классов, которые, возможно, должны знать о пользовательском интерфейсе. Разве класс, не знающий, как рисовать себя, не нарушит некоторые лучшие практики, так как это зависит от того, каков пользовательский интерфейс (консоль, графический интерфейс и т. Д.)?
Во многих книгах по программированию я встречал пример «Shape», который показывает наследование. Форма базового класса имеет метод draw (), который переопределяет каждая форма, такая как круг и квадрат. Это учитывает полиморфизм. Но разве метод draw () не сильно зависит от пользовательского интерфейса? Если мы напишем этот класс, скажем, Win Forms, то мы не сможем повторно использовать его для консольного приложения или веб-приложения. Это верно?
Причина этого вопроса в том, что я всегда застреваю и зацикливаюсь на том, как обобщать классы, чтобы они были наиболее полезными. Это на самом деле работает против меня, и мне интересно, если я "слишком стараюсь".
Shape
класс, то вы, вероятно, пишете сам графический стек, а не записываете клиент в графический стек.Ответы:
Это зависит от класса и варианта использования. Визуальный элемент, зная, как рисовать себя, не обязательно является нарушением принципа единой ответственности.
Опять не обязательно. Если вы можете создать интерфейс (drawPoint, drawLine, установить Color и т. Д.), Вы можете в значительной степени передать любой контекст для рисования чего-либо на фигуре, например в конструкторе фигуры. Это позволило бы фигурам рисовать себя на консоли или любом заданном холсте.
Ну, это правда. Если вы напишите UserControl (не класс в целом) для Windows Forms, вы не сможете использовать его с консолью. Но это не проблема. Почему вы ожидаете, что UserControl для Windows Forms будет работать с любой презентацией? UserControl должен делать одну вещь и делать это хорошо. Это связано с определенной формой представления по определению. В конце концов, пользователю нужно что-то конкретное, а не абстракция. Это может быть только частично верно для фреймворков, но для приложений конечного пользователя это так.
Однако логика, стоящая за ним, должна быть отделена, чтобы вы могли использовать ее снова с другими технологиями презентации. При необходимости вводите интерфейсы, чтобы поддерживать ортогональность для вашего приложения. Общее правило: конкретные вещи должны обмениваться с другими конкретными вещами.
Вы знаете, экстремальные программисты любят свое отношение к YAGNI . Не пытайтесь писать все в общих чертах и не старайтесь изо всех сил пытаться сделать все общее назначение. Это называется чрезмерной инженерией и в конечном итоге приведет к совершенно запутанному коду. Дайте каждому компоненту ровно одну задачу и убедитесь, что она справляется. При необходимости вставляйте абстракции, где вы ожидаете, что что-то изменится (например, интерфейс для рисования контекста, как указано выше).
В общем, при написании бизнес-приложений вы всегда должны пытаться отделить вещи. MVC и MVVM отлично подходят для отделения логики от презентации, поэтому вы можете использовать ее для веб-презентации или консольного приложения. Имейте в виду, что в конце некоторые вещи должны быть конкретными. Ваши пользователи не могут работать с абстракцией, им нужно что-то конкретное. Абстракции - это всего лишь помощники для вас, программиста, для того, чтобы код был расширяемым и обслуживаемым. Вам нужно подумать о том, где ваш код должен быть гибким. В конце концов все абстракции должны породить что-то конкретное.
Изменить: Если вы хотите узнать больше об архитектуре и методах проектирования, которые могут предоставить лучшие практики, я предлагаю вам прочитать ответ @Catchops и прочитать о методах SOLID в Википедии.
Кроме того, для начала я всегда рекомендую следующую книгу: Head First Design Patterns . Это поможет вам понять методы абстрагирования / разработки ООП, в большей степени, чем книга GoF (что отлично, просто не подходит новичкам).
источник
Вы определенно правы. И нет, вы "просто отлично пытаетесь" :)
Читайте о принципе единой ответственности
Ваша внутренняя работа в классе и то, как эта информация должна быть представлена пользователю, являются двумя обязанностями.
Не бойтесь разъединять занятия. Редко проблема в слишком большой абстракции и развязке :)
Два очень важных шаблона - это модель – представление – контроллер для веб-приложений и модель View ViewModel для Silverlight / WPF.
источник
Я часто использую MVVM, и, по моему мнению, класс бизнес-объектов никогда не должен знать ничего о пользовательском интерфейсе. Конечно, им может понадобиться знать
SelectedItem
, илиIsChecked
, илиIsVisible
, и т. Д., Но эти значения не обязательно должны быть привязаны к какому-либо конкретному интерфейсу и могут быть общими свойствами класса.Если вам нужно что-то сделать с интерфейсом в коде, например, настройкой Focus, запуском анимации, обработкой горячих клавиш и т. Д., Тогда код должен быть частью кода позади пользовательского интерфейса, а не классами бизнес-логики.
Поэтому я бы сказал, не переставайте пытаться разделить ваш пользовательский интерфейс и ваши классы. Чем больше они разъединены, тем легче их поддерживать и проверять.
источник
Есть много проверенных и настоящих шаблонов проектирования, которые были разработаны за эти годы, чтобы точно соответствовать тому, о чем вы говорите. Другие ответы на ваш вопрос касались принципа единой ответственности - который является абсолютно действительным - и что, по-видимому, движет вашим вопросом. Этот принцип просто утверждает, что класс должен делать одну вещь ХОРОШО. Другими словами, повышение Cohesion и понижение Coupling - вот что такое хороший объектно-ориентированный дизайн - делает ли класс одно дело хорошо и не имеет много зависимостей от других.
Ну ... вы правы, заметив, что если вы хотите нарисовать круг на iPhone, он будет отличаться от рисунка на ПК под управлением Windows. Вы ДОЛЖНЫ иметь (в данном случае) конкретный класс, который рисует одну лунку на iPhone, а другой - одну лунку на ПК. Вот где основной ОО-принцип наследования, который разбивают все эти примеры форм. Вы просто не можете сделать это только по наследству.
Именно здесь вступают интерфейсы - как утверждает «Бригада четырех» (ДОЛЖНА ПРОЧИТАТЬ) - Всегда отдавайте предпочтение реализации, а не наследованию. Другими словами, используйте интерфейсы для объединения архитектуры, которая может выполнять различные функции разными способами, не полагаясь на жестко закодированные зависимости.
Я видел Referece к ТВЕРДЫМ принципам. Это здорово. «S» - это принцип единственной ответственности. НО, «D» означает инверсию зависимости. Здесь можно использовать шаблон Inversion of Control (Dependency Injection). Он очень мощный и может использоваться для ответа на вопрос о том, как спроектировать систему, которая может нарисовать круг для iPhone, а также для ПК.
Можно создать архитектуру, которая содержит общие бизнес-правила и доступ к данным, но имеет различные реализации пользовательских интерфейсов, использующих эти конструкции. Это действительно помогает, однако, быть в команде, которая внедрила это, и увидеть это в действии, чтобы действительно понять это.
Это просто быстрый высокоуровневый ответ на вопрос, который заслуживает более подробного ответа. Я призываю вас заглянуть в эти паттерны. Можно найти более конкретную реализацию этих шаблонов, а также хорошо известные имена MVC и MVVM.
Удачи!
источник
В этом случае вы все еще можете использовать MVC / MVVM и внедрять различные реализации пользовательского интерфейса, используя общий интерфейс:
Таким образом, вы сможете повторно использовать логику контроллера и модели, в то же время добавляя новые типы GUI.
источник
Для этого есть разные шаблоны: MVP, MVC, MVVM и т. Д.
Хорошая статья Мартина Фаулера (известное имя) для чтения - GUI Architectures: http://www.martinfowler.com/eaaDev/uiArchs.html
MVP пока не упоминается, но на него, безусловно, стоит упомянуть: взгляните на это.
Это шаблон, предложенный разработчиками Google Web Toolkit для использования, он действительно аккуратный.
Вы можете найти реальный код, реальные примеры и обоснование того, почему этот подход полезен здесь:
http://code.google.com/webtoolkit/articles/mvp-architecture.html
http://code.google.com/webtoolkit/articles/mvp-architecture-2.html
Одно из преимуществ использования этого или аналогичных подходов, которое здесь недостаточно подчеркивается, - это тестируемость! В большинстве случаев я бы сказал, что это главное преимущество!
источник
Это одно из мест, где ООП терпит неудачу с задачей абстракции. ООП полиморфизм использует динамическую диспетчеризацию по одной переменной ('this'). Если полиморфизм был основан на Shape, то вы не можете отправить полиморфно-союзников на рендерер (консоль, графический интерфейс и т. Д.).
Рассмотрим систему программирования, которая может распределяться по двум или более переменным:
и также предположим, что система может дать вам способ выразить poly_draw для различных комбинаций типов Shape и Renderer. Тогда было бы легко придумать классификацию форм и средств визуализации, верно? Проверка типов как-то поможет вам понять, есть ли комбинации формы и рендера, которые вы, возможно, пропустили.
Большинство языков ООП не поддерживают ничего подобного (некоторые делают, но они не являются основными). Я бы посоветовал вам взглянуть на шаблон посетителя для обходного пути.
источник
Выше звучит правильно для меня. Насколько я понимаю, можно сказать, что это означает относительно тесную связь между Controller и View с точки зрения шаблона проектирования MVC. Это также означает, что для переключения между desktop-console-webapp необходимо будет соответственно переключить Controller и View в виде пары - только модель остается неизменной.
Что ж, мое текущее мнение о том, что соединение между View-Controller, о котором мы говорим, в порядке, и даже более, оно довольно модно. в современном дизайне.
Хотя год или два назад я тоже чувствовал себя неуверенно по этому поводу. Я передумал после изучения дискуссий на форуме Sun по шаблонам и ОО-дизайну.
Если вам интересно, попробуйте этот форум самостоятельно - теперь он перенесен в Oracle ( ссылка ). Если вы попали туда, попробуйте пинговать парня Сайша - тогда его объяснения по этим сложным вопросам оказались для меня наиболее полезными. Я не могу сказать, хотя он все еще участвует - я сам там не был в течение долгого времени
источник
С прагматической точки зрения, некоторый код в вашей системе должен знать, как нарисовать что-то вроде
Rectangle
если это требование конечного пользователя. И в какой-то момент все сводится к выполнению действительно низкоуровневых вещей, таких как растеризация пикселей или отображение чего-либо в консоли.Вопрос для меня с точки зрения связи: кто / что должен зависеть от этого типа информации и от какой степени детализации (например, абстрактной)?
Абстрагирование возможностей рисования / рендеринга
Потому что, если код рисования более высокого уровня зависит только от чего-то очень абстрактного, эта абстракция может работать (путем замены конкретных реализаций) на всех платформах, на которые вы собираетесь ориентироваться. В качестве надуманного примера, некоторые очень абстрактные
IDrawer
интерфейсы могут быть реализованы как в консоли, так и в GUI API, чтобы выполнять такие вещи, как формы графиков (реализация консоли может обрабатывать консоль как некое «изображение» 80xN с использованием ASCII-графики). Конечно, это надуманный пример, поскольку обычно это не то, что вам нужно, это обрабатывать вывод консоли как буфер изображений / кадров; как правило, большинству пользователей требуется больше текстовых взаимодействий в консолях.Еще один вопрос: насколько легко создать стабильную абстракцию? Потому что это может быть легко, если все, на что вы нацеливаетесь, - это современные API-интерфейсы GUI для абстрагирования основных возможностей рисования фигур, таких как построение линий, прямоугольников, путей, текста и тому подобное (просто простая растеризация 2D ограниченного набора примитивов) с одним абстрактным интерфейсом, который может быть легко реализован для них через различные подтипы с минимальными затратами. Если вы можете эффективно спроектировать такую абстракцию и реализовать ее на всех целевых платформах, то я бы сказал, что это гораздо меньшее зло, если даже вообще не зло, для формы или элемента управления графическим интерфейсом или что-то еще, чтобы знать, как рисовать себя, используя такую абстракция.
Но, скажем, вы пытаетесь абстрагироваться от мрачных деталей, которые различаются между Playstation Portable, iPhone, XBox One и мощным игровым ПК, в то время как вы должны использовать самые передовые технологии 3D-рендеринга / затенения в реальном времени на каждом из них. , В этом случае попытка придумать один абстрактный интерфейс для абстрагирования деталей рендеринга, когда базовые аппаратные возможности и API-интерфейсы настолько дико изменяются, почти наверняка приведет к огромному времени на разработку и перепроектирование, высокую вероятность повторяющихся изменений дизайна с непредвиденными открытия, а также решение с наименьшим общим знаменателем, которое не в состоянии использовать полную уникальность и мощь базового оборудования.
Обеспечение зависимости от стабильных, «легких» конструкций
В моей области я в этом последнем сценарии. Мы нацелены на множество разного оборудования с принципиально разными базовыми возможностями и API, и попытаться придумать одну абстракцию рендеринга / рисования, чтобы управлять ими всеми, безгранично безнадежно (мы можем стать всемирно известными, просто делая это эффективно, как в игре сменщик в отрасли). Поэтому последнее , что я хочу в моем случае это как аналоговая
Shape
илиModel
илиParticle Emitter
что умеет рисовать себя, даже если он выражает , что рисунок на самом высоком уровне и , возможно , наиболее абстрактный способ ...... потому что эти абстракции слишком сложны для правильного проектирования, и когда дизайн трудно исправить, и все зависит от него, это рецепт для самых дорогостоящих центральных изменений дизайна, которые пульсируют и ломают все в зависимости от этого. Поэтому последнее, что вам нужно, - это чтобы зависимости в ваших системах направлялись к абстрактным проектам, слишком трудно их исправить (слишком сложно стабилизировать без навязчивых изменений).
Сложный Зависит от Легко, Не Легко Зависит от Трудно
Вместо этого мы делаем так, чтобы зависимости перетекли в вещи, которые легко спроектировать. Гораздо проще спроектировать абстрактную «Модель», которая просто сфокусирована на хранении таких вещей, как многоугольники и материалы, и придать ей правильную конструкцию, чем спроектировать абстрактный «Рендерер», который можно эффективно реализовать (с помощью заменяемых конкретных подтипов) для обслуживания чертежей единообразно запрашивает аппаратное обеспечение, такое же, как PSP от ПК.
Таким образом, мы инвертируем зависимости от вещей, которые сложно спроектировать. Вместо того, чтобы заставлять абстрактные модели знать, как рисовать себя в дизайне абстрактного рендерера, от которого они все зависят (и нарушать свои реализации, если этот дизайн меняется), у нас вместо этого есть абстрактный рендерер, который знает, как рисовать каждый абстрактный объект в нашей сцене ( модели, излучатели частиц и т. д.), и поэтому мы можем затем реализовать подтип рендерера OpenGL для ПК, например
RendererGl
, другой для PSPRendererPsp
, другой для мобильных телефонов и т. д. В этом случае зависимости стремятся к стабильным проектам, их легко получить правильно, от рендера до различных типов объектов (моделей, частиц, текстур и т. д.) в нашей сцене, а не наоборот.Если вы нашли себя, пытаясь абстрагироваться от чего-то на центральном уровне вашего кодовую и это слишком сложно разработать, вместо того, чтобы упорно биться головой о стены и постоянно делает навязчивые изменения к нему каждый месяц / год, что требует обновления 8000 исходных файлов, так как это ломая все, что от него зависит, мое первое предложение - рассмотреть возможность обращения зависимостей. Посмотрите, сможете ли вы написать код таким образом, чтобы вещь, которую так сложно спроектировать, зависела от всего, что легче спроектировать, не имея вещей, которые проще спроектировать, в зависимости от того, что так сложно спроектировать. Обратите внимание, что я имею в виду проекты (в частности, дизайн интерфейсов), а не реализации: иногда вещи легко проектировать и сложно реализовать, а иногда вещи сложно спроектировать, но легко реализовать. Зависимости перетекают к проектам, поэтому следует сосредоточиться только на том, насколько сложно что-то спроектировать здесь, чтобы определить направление, в котором текут зависимости.
Принцип единой ответственности
Для меня SRP здесь обычно не так интересен (хотя в зависимости от контекста). Я имею в виду, что при проектировании вещей, которые ясны по своему назначению и поддержанию, есть жесткая балансировка, но вашим
Shape
объектам, возможно, придется предоставить более подробную информацию, если, например, они не знают, как рисовать себя, и может быть не так много значимых вещей для сделать с формой в конкретном контексте использования, чем построить его и нарисовать его. Есть компромиссы практически со всем, и это не связано с SRP, который может заставить вещи осознать, как сделать себя способным стать таким кошмаром обслуживания в моем опыте в определенных контекстах.Это гораздо больше связано со связью и направлением распространения зависимостей в вашей системе. Если вы пытаетесь портировать абстрактный интерфейс рендеринга, от которого все зависит (потому что они используют его для рисования самих себя), к новому целевому API / аппаратному обеспечению и понимаете, что вам нужно значительно изменить его дизайн, чтобы заставить его работать там эффективно, тогда это очень дорогостоящее изменение, которое требует замены реализаций всего в вашей системе, которая умеет рисовать себя. И это наиболее практичная проблема обслуживания, с которой я сталкиваюсь, когда люди осознают, как рисовать себя, если это приводит к множеству зависимостей, перетекающих в абстракции, которые слишком сложно правильно спроектировать заранее.
Гордость разработчика
Я упоминаю об этом, потому что, по моему опыту, это часто является самым большим препятствием на пути направления зависимостей к вещам, которые легче спроектировать. Разработчикам очень легко стать немного амбициозными и сказать: «Я собираюсь разработать кроссплатформенную абстракцию рендеринга, чтобы управлять ими всеми, я решу, что другие разработчики тратят месяцы на портирование, и я собираюсь получить это правильно, и это будет работать как волшебство на каждой платформе, которую мы поддерживаем, и использовать самые современные методы рендеринга на каждой; я уже думал об этом ».В этом случае они отказываются от практического решения, которое состоит в том, чтобы избежать этого и просто изменить направление зависимостей и перевести то, что может быть чрезвычайно дорогостоящим, и повторяющиеся центральные изменения проекта в просто дешевые и локальные повторяющиеся изменения в реализации. В разработчиках должен быть какой-то инстинкт «белого флага», чтобы сдаться, когда что-то слишком сложно спроектировать до такого абстрактного уровня, и пересмотреть всю свою стратегию, в противном случае их ждет много горя и боли. Я бы посоветовал перенести такие амбиции и боевой дух в современные реализации, которые легче спроектировать, чем перенести такие покоряющие мир амбиции на уровень дизайна интерфейса.
источник
OpenGLBillboard
вы создадите a,OpenGLRenderer
который умеет рисовать любой типIBillBoard
? Но будет ли он делать это путем делегирования логикиIBillBoard
или будет иметь огромные переключатели илиIBillBoard
типы с условными выражениями ? Это то, что мне трудно понять, потому что это совсем не похоже на ремонтопригодность!RendererPsp
объект, который знает высокоуровневые абстракции вашей игровой сцены, он может затем делать всю магию и сальто, необходимые для рендеринга таких вещей способом, который выглядит убедительно на PSP ....BillBoard
зависимость, будет действительно трудно? Принимая во внимание, чтоIRenderer
уровень a уже высок и может зависеть от этих проблем с гораздо меньшими проблемами?