Недавно у меня был разговор с моим другом об ООП в разработке видеоигр.
Я объяснял архитектуру одной из моих игр, которая, к удивлению моего друга, содержала много небольших классов и несколько уровней абстракции. Я утверждал, что это было результатом того, что я сосредоточился на том, чтобы дать всем Единую Ответственность, а также ослабить связь между компонентами.
Его беспокоило то, что большое количество занятий перерастет в кошмар обслуживания. На мой взгляд, это будет иметь совершенно противоположный эффект. Мы продолжали обсуждать это в течение, казалось бы, столетий, в конце концов соглашаясь не соглашаться, говоря, что, возможно, были случаи, когда принципы SOLID и надлежащий ООП фактически не смешивались.
Даже статья в Википедии о принципах SOLID гласит, что они являются руководящими принципами, которые помогают в написании поддерживаемого кода, и что они являются частью общей стратегии гибкого и адаптивного программирования.
Итак, мой вопрос:
Есть ли в ООП случаи, когда некоторые или все принципы SOLID не пригодны для очистки кода?
Я могу сразу представить, что принцип замещения Лискова может конфликтовать с другим видом безопасного наследования. То есть, если кто-то изобрел другой полезный шаблон, реализованный с помощью наследования, вполне возможно, что LSP может вступить в прямой конфликт с ним.
Есть ли другие? Возможно, определенные типы проектов или определенные целевые платформы работают лучше с менее твердым подходом?
Редактировать:
Я просто хотел бы указать, что я не спрашиваю, как улучшить мой код;) Единственная причина, по которой я упомянул проект в этом вопросе, - дать немного контекста. Мой вопрос о ООП и принципах проектирования в целом .
Если вам интересно узнать о моем проекте, посмотрите это .
Изменить 2:
Я предполагал, что на этот вопрос ответят одним из 3 способов:
- Да, существуют принципы проектирования ООП, которые частично противоречат SOLID
- Да, существуют принципы проектирования ООП, которые полностью противоречат SOLID
- Нет, SOLID - это колени пчелы, и ООП всегда будет лучше с ним. Но, как и во всем, это не панацея. Пить ответственно.
Варианты 1 и 2, вероятно, дали бы длинные и интересные ответы. Вариант 3, с другой стороны, будет коротким, неинтересным, но в целом обнадеживающим ответом.
Мы, кажется, сходимся к варианту 3.
источник
Ответы:
В общем нет. История показала, что принципы SOLID в значительной степени способствуют усилению развязки, что, в свою очередь, повышает гибкость кода и, следовательно, вашу способность приспосабливаться к изменениям, а также упрощает анализ, тестирование, повторное использование кода. короче, сделай свой код чище.
Теперь могут быть случаи, когда принципы SOLID сталкиваются с DRY (не повторяйте себя), KISS (пусть это будет просто глупо) или другими принципами хорошего дизайна ОО. И, конечно, они могут столкнуться с реальностью требований, ограничений людей, ограничений наших языков программирования, других препятствий.
Короче говоря, принципы SOLID всегда пригодны для чистого кода, но в некоторых сценариях они дают меньше, чем конфликтующие альтернативы. Они всегда хороши, но иногда другие вещи более хороши.
источник
Count
иasImmutable
, и свойства, такие какgetAbilities
[чье возвращение будет указывать, будут ли такие вещиCount
«эффективными»], тогда можно иметь статический метод, который принимает несколько последовательностей и объединяет их так, чтобы они Вы будете вести себя как одна более длинная последовательность. Если такие способности присутствуют в базовом типе последовательности, даже если они связаны только с реализациями по умолчанию, тогда совокупность сможет раскрыть эти способности ...Я думаю, что у меня необычная перспектива в том, что я работал в сфере финансов и игр.
Многие программисты, с которыми я встречался в играх, были ужасны в Software Engineering, но им не нужны были такие практики, как SOLID. Традиционно они помещают свою игру в коробку - тогда они сделаны.
В финансах я обнаружил, что разработчики могут быть очень небрежными и недисциплинированными, потому что им не нужна производительность, которую вы делаете в играх.
Оба приведенных выше утверждения, конечно, являются чрезмерными обобщениями, но на самом деле в обоих случаях чистый код является критическим. Для оптимизации необходим простой и понятный код. Ради удобства, вы хотите то же самое.
Нельзя сказать, что SOLID не без критики. Их называют принципами, и, тем не менее, они должны следовать принципам? Так когда именно я должен следовать за ними? Когда я должен нарушать правила?
Вы могли бы, вероятно, взглянуть на два фрагмента кода и сказать, какой из них лучше всего соответствует SOLID. Но в отдельности не существует объективного способа преобразовать код в SOLID. Это определенно для интерпретации разработчика.
Нельзя сказать, что «большое количество классов может привести к кошмару обслуживания». Однако, если SRP интерпретируется неправильно, вы легко можете столкнуться с этой проблемой.
Я видел код со многими уровнями практически без ответственности. Классы, которые ничего не делают, кроме передачи состояния из одного класса в другой. Классическая проблема слишком много слоев косвенности.
SRP в случае злоупотребления может привести к отсутствию сплоченности в вашем коде. Вы можете сказать это при попытке добавить функцию. Если вам всегда приходится менять несколько мест одновременно, то вашему коду не хватает сплоченности.
Open-Closed не без своих критиков. Например, см. Обзор Джона Скита по принципу открытого закрытого типа . Я не буду воспроизводить его аргументы здесь.
Ваш аргумент о LSP кажется довольно гипотетическим, и я не совсем понимаю, что вы подразумеваете под другой формой безопасного наследования?
Интернет-провайдер, кажется, несколько дублирует SRP. Конечно, они должны интерпретироваться так же, как вы никогда не можете предвидеть, что будут делать все клиенты вашего интерфейса. Сегодняшний целостный интерфейс - завтрашний нарушитель интернет-провайдера. Единственный нелогичный вывод - иметь не более одного члена на интерфейс.
DIP может привести к неработоспособному коду. Я видел классы с огромным количеством параметров от имени DIP. Эти классы обычно «обновляют» свои составные части, но я называю способность к тестированию, что мы никогда не должны снова использовать ключевое слово new.
SOLID сам по себе недостаточно для написания чистого кода. Я видел книгу Роберта К. Мартина « Чистый код: руководство по гибкому программному обеспечению ». Самая большая проблема в том, что эту книгу легко читать и интерпретировать как правила. Если вы делаете это, вы упускаете суть. Он содержит большой список запахов, эвристик и принципов - гораздо больше, чем просто пять из SOLID. Все эти «принципы» на самом деле не принципы, а руководящие принципы. Дядя Боб сам описывает их как "дырки" для понятий. Они уравновешивают; слепое следование любым указаниям вызовет проблемы.
источник
Вот мое мнение:
Хотя принципы SOLID нацелены на не избыточную и гибкую кодовую базу, это может быть компромиссом в удобочитаемости и обслуживании, если существует слишком много классов и уровней.
Особенно это касается количества абстракций . Большое количество классов может быть в порядке, если они основаны на нескольких абстракциях. Поскольку мы не знаем размера и специфики вашего проекта, это трудно сказать, но немного неприятно, что вы упоминаете несколько слоев в дополнение к большому количеству классов.
Я полагаю, что принципы SOLID не противоречат ООП, но все же возможно написать неочищенный код, придерживающийся их.
источник
В ранние годы разработки я начал писать Roguelike на C ++. Поскольку я хотел применить хорошую объектно-ориентированную методологию, которую я узнал из своего образования, я сразу же увидел, что игровые предметы - это объекты C ++.
Potion
,Swords
,Food
,Axe
, ИJavelins
т.д. все происходит от базового основногоItem
кода, обработки имени, значка, вес, что таких вещей.Затем я кодировал сумку логику и столкнулся с проблемой. Я могу положить
Items
в сумку, но тогда, если я выберу один, как я узнаю, если этоPotion
илиSword
? Я посмотрел в Интернете, как убрать предметы. Я взломал опции компилятора, чтобы включить информацию времени выполнения, чтобы я знал, каковы мои абстрагированныеItem
истинные типы, и начал переключать логику на типы, чтобы знать, какие операции я бы разрешил (например, избегать питьSwords
и ставить наPotions
ноги)По сути, он превратил код в большой шарик из полузакрытой грязи. Когда проект продолжался, я начал понимать, что было неудачей проекта. Случилось так, что я попытался использовать классы для представления значений. Моя классовая иерархия не была значимой абстракцией. Что я должен был сделать это делает
Item
реализовать набор функций, таких , какEquip ()
,Apply ()
, иThrow ()
, и сделать каждый ведут себя на основе значений. Использование перечислений для представления игровых автоматов. Использование перечислений для представления различных видов оружия. Использование большего количества значений и меньше классификации, потому что у моего подкласса не было никакой другой цели, кроме как заполнить эти окончательные значения.Поскольку вы сталкиваетесь с умножением объектов на уровне абстракции, я думаю, что мое понимание может быть полезным здесь. Если кажется, что у вас слишком много типов объектов, возможно, вы путаете то, что должно быть типом, с тем, что должно быть значением.
источник
Я абсолютно на стороне вашего друга, но это может зависеть от наших доменов, типов проблем и конструкций, которые мы решаем, и особенно от того, какие типы вещей могут потребовать изменений в будущем. Разные проблемы, разные решения. Я не верю в правильное или неправильное, просто программисты пытаются найти лучший способ, которым они могут лучше всего решить свои конкретные проблемы дизайна. Я работаю в VFX, который не слишком отличается от игровых движков.
Но проблема для меня, с которой я боролся в том, что, по крайней мере, можно было бы назвать несколько более похожей на SOLID-совместимую архитектуру (она была на основе COM), может грубо сводиться к «слишком большому количеству классов» или «слишком большому количеству функций», так как твой друг мог бы описать. Я бы конкретно сказал: «слишком много взаимодействий, слишком много мест, которые могли бы плохо себя вести, слишком много мест, которые могли бы вызвать побочные эффекты, слишком много мест, которые, возможно, нужно изменить, и слишком много мест, которые могли бы не делать то, что мы думаем, что они делают». «.
У нас было несколько абстрактных (и чистых) интерфейсов, реализованных множеством подтипов, вроде этого (сделал эту диаграмму в контексте обсуждения преимуществ ECS, не обращая внимания на нижний левый комментарий):
Где интерфейс движения или интерфейс узла сцены могут быть реализованы с помощью сотен подтипов: источников света, камер, сеток, решателей физики, шейдеров, текстур, костей, примитивных форм, кривых и т. Д. И т. Д. (И часто бывают разные типы каждого ). И главная проблема заключалась в том, что эти проекты были не такими стабильными. У нас были изменяющиеся требования, и иногда сами интерфейсы приходилось менять, и когда вы хотите изменить абстрактный интерфейс, реализованный на 200 подтипах, это чрезвычайно дорогое изменение. Мы начали смягчать это, используя абстрактные базовые классы, между которыми снижались затраты на такие изменения дизайна, но они все еще были дорогими.
Итак, в качестве альтернативы я начал исследовать архитектуру системы сущностей и компонентов, довольно часто используемую в игровой индустрии. Это изменило все, чтобы быть таким:
И вау! Это была такая разница с точки зрения ремонтопригодности. Зависимости перешли не к абстракциям , а к данным (компонентам). И, по крайней мере, в моем случае, данные были гораздо более стабильными и их было проще понять с точки зрения проектирования, несмотря на изменяющиеся требования (хотя то, что мы можем делать с одними и теми же данными, постоянно меняется с изменением требований).
Кроме того, поскольку объекты в ECS используют композицию вместо наследования, им фактически не нужно содержать функциональность. Они просто аналогичный «контейнер компонентов». Это сделало так, что аналогичные 200 подтипов, которые реализовали интерфейс движения, превращаются в 200 экземпляров сущности (не отдельные типы с отдельным кодом), которые просто хранят компонент движения (который является ничем иным, как данными, связанными с движением). A
PointLight
больше не является отдельным классом / подтипом. Это не класс вообще. Это экземпляр объекта, который просто комбинирует некоторые компоненты (данные), связанные с тем, где он находится в пространстве (движение), и конкретными свойствами точечных источников света. Единственная функциональность, связанная с ними, находится внутри систем, таких какRenderSystem
, который ищет компоненты света в сцене, чтобы определить, как визуализировать сцену.С изменением требований в соответствии с подходом ECS часто возникала необходимость изменить только одну или две системы, работающие с этими данными, или просто ввести новую систему на стороне, или ввести новый компонент, если потребовались новые данные.
Так что, по крайней мере, для моего домена, и я почти уверен, что это не для всех, это сильно упростило ситуацию, потому что зависимости стремились к стабильности (вещи, которые не нужно часто менять вообще). Это не имело место в архитектуре COM, когда зависимости равномерно стекались к абстракциям. В моем случае гораздо проще выяснить, какие данные требуются для первоначального движения, а не все возможные вещи, которые вы могли бы с ним сделать, что часто меняется с течением месяцев или лет, когда появляются новые требования.
Ну, чистый код я не могу сказать, так как некоторые люди приравнивают чистый код к SOLID, но определенно есть некоторые случаи, когда отделение данных от функциональности, как это делает ECS, и перенаправление зависимостей от абстракций к данным, безусловно, может сделать вещи намного проще. измените, по очевидным причинам связи, если данные будут намного более стабильными, чем абстракции. Конечно, зависимости от данных могут затруднить поддержание инвариантов, но ECS стремится свести это к минимуму с помощью системной организации, которая минимизирует количество систем, которые получают доступ к компоненту любого типа.
Это не обязательно, что зависимости должны течь к абстракциям, как предполагает DIP; зависимости должны стремиться к вещам, которые вряд ли нуждаются в будущих изменениях. Это может или не может быть абстракциями во всех случаях (это, конечно, не в моем).
Я не уверен, является ли ECS действительно ароматом ООП. Некоторые люди определяют это таким образом, но я вижу, что это очень сильно отличается от характеристик связи и отделения данных (компонентов) от функциональности (систем) и отсутствия инкапсуляции данных. Если бы это считалось формой ООП, я бы подумал, что он очень сильно конфликтует с SOLID (по крайней мере, самые строгие идеи SRP, open / closed, подстановка liskov и DIP). Но я надеюсь, что это разумный пример одного случая и области, где самые фундаментальные аспекты SOLID, по крайней мере, так как люди обычно интерпретируют их в более узнаваемом контексте ООП, могут быть не применимы.
Крошечные классы
ECS бросил вызов и сильно изменил мои взгляды. Как и вы, я привык думать, что сама идея ремонтопригодности состоит в том, чтобы иметь простейшую реализацию для возможных вещей, что подразумевает много вещей и, кроме того, много взаимозависимых вещей (даже если взаимозависимости находятся между абстракциями). Это имеет смысл, если вы увеличиваете только один класс или функцию, чтобы увидеть наиболее простую и простую реализацию, а если мы ее не видим, реорганизуйте ее и, возможно, даже разложите дальше. Но в результате может быть легко пропустить то, что происходит с внешним миром, потому что всякий раз, когда вы делите что-либо относительно сложное на две или более вещи, эти две или более вещи неизбежно должны взаимодействовать * (см. Ниже) друг с другом в некоторых Кстати, или что-то снаружи должно взаимодействовать со всеми ними.
В эти дни я обнаружил, что существует баланс между простотой чего-либо и количеством вещей, и сколько требуется взаимодействия. Системы в ECS имеют тенденцию быть довольно здоровенными с нетривиальными реализациями для работы с данными, такими как
PhysicsSystem
илиRenderSystem
илиGuiLayoutSystem
. Однако тот факт, что сложному продукту так мало нужно, имеет тенденцию облегчать отступление и рассуждать об общем поведении всей кодовой базы. Там есть что-то, что может указывать на то, что неплохо было бы полагаться на меньшее количество более объемных классов (по-прежнему выполняющих, возможно, единственную ответственность), если это означает, что нужно поддерживать меньше классов, о которых нужно думать, и меньше взаимодействий на протяжении всего процесса. система.взаимодействия
Я говорю «взаимодействия», а не «связывание» (хотя сокращение взаимодействий подразумевает уменьшение обоих), поскольку вы можете использовать абстракции для разделения двух конкретных объектов, но они по-прежнему взаимодействуют друг с другом. Они все еще могут вызывать побочные эффекты в процессе этого косвенного общения. И часто я нахожу, что моя способность рассуждать о правильности системы связана с этими «взаимодействиями» больше, чем с «связью». Минимизация взаимодействий, как правило, облегчает мне понимание всего с высоты птичьего полета. Это означает, что вещи вообще не разговаривают друг с другом, и в этом смысле ECS также имеет тенденцию действительно минимизировать «взаимодействия», а не просто связь с минимумом минимумов (по крайней мере, я не имею
Тем не менее, это может быть, по крайней мере, частично я и мои личные слабости. Я обнаружил, что самым большим препятствием для меня является создание систем огромного масштаба, и я до сих пор уверенно рассуждаю о них, перемещаюсь по ним и чувствую, что могу вносить любые потенциальные желаемые изменения в любом месте предсказуемым образом, это управление состоянием и ресурсами наряду с побочные эффекты. Это самое большое препятствие, которое начинает возникать при переходе от десятков тысяч LOC к сотням тысяч LOC к миллионам LOC, даже для кода, который я полностью создал самостоятельно. Если что-то замедлит меня до уровня выше всего, это чувство, что я больше не могу понять, что происходит с точки зрения состояния приложения, данных, побочных эффектов. Это' Это не роботизированное время, которое требуется для внесения изменений, которое замедляет меня настолько сильно, как неспособность понять все последствия изменений, если система выходит за пределы способности моего разума рассуждать об этом. И сокращение количества взаимодействий стало для меня наиболее эффективным способом, позволяющим значительно увеличить объем продукта и значительно расширить его возможности, при этом я лично не перегружен этими вещами, поскольку сокращение количества взаимодействий до минимума также уменьшает количество мест, которые могут даже возможно изменить состояние приложения и вызвать побочные эффекты существенно.
Это может обернуться чем-то вроде этого (где все на диаграмме имеет функциональность, и, очевидно, сценарий реального мира будет иметь много-много раз количество объектов, и это диаграмма взаимодействия, а не связь, как связь между ними будут абстракции):
... для этого, где только системы имеют функциональность (синие компоненты теперь являются просто данными, а теперь это диаграмма связи):
И по этому поводу возникают мысли, и, возможно, есть способ создать некоторые из этих преимуществ в более подходящем контексте ООП, более совместимом с SOLID, но я пока не совсем нашел конструкции и слова, и я нахожу это Трудно, так как терминология я привык разбрасывать все, что непосредственно связано с ООП. Я продолжаю пытаться выяснить это, читая ответы людей здесь, а также стараюсь изо всех сил сформулировать свои собственные, но есть некоторые вещи, очень интересные в природе ECS, которые я не смог точно указать на это. может быть более широко применимым даже к архитектурам, которые его не используют. Я также надеюсь, что этот ответ не сработает как акция ECS! Я просто нахожу это очень интересным, так как проектирование ECS действительно сильно изменило мои мысли,
источник
Принципы SOLID хороши, но KISS и YAGNI отдают приоритет в случае конфликтов. Целью SOLID является управление сложностью, но если применение самого SOLID делает код более сложным, это противоречит цели. Например, если у вас небольшая программа с несколькими классами, применение DI, сегрегация интерфейса и т. Д. Вполне может увеличить общую сложность программы.
Принцип открытия / закрытия является особенно спорным. В основном это говорит о том, что вы должны добавить функции в класс, создав подкласс или отдельную реализацию того же интерфейса, но оставив исходный класс без изменений. Если вы поддерживаете библиотеку или службу, используемую несогласованными клиентами, это имеет смысл, поскольку вы не хотите нарушать поведение, на которое может рассчитывать выходящий клиент. Однако, если вы изменяете класс, который используется только в вашем приложении, часто проще и проще изменить класс.
источник
По моему опыту:-
Большие классы экспоненциально труднее поддерживать, поскольку они становятся больше.
Маленькие классы легче поддерживать, а сложность поддержания коллекции маленьких классов арифметически возрастает до количества классов.
Иногда большие классы неизбежны, большая проблема иногда требует большого класса, но их гораздо труднее читать и понимать. Для хорошо названных небольших классов только имени может быть достаточно для понимания, вам даже не нужно смотреть на код, если только нет особой проблемы с классом.
источник
Мне всегда трудно провести черту между «S» твердого тела и (может быть, старомодно) необходимостью дать объекту все знания о себе.
15 лет назад я работал с разработчиками Deplhi, у которых был святой Грааль, чтобы у них были классы, в которых было все. Таким образом, для ипотеки вы можете добавить страховки и платежи, а также доход и кредиты и налоговую информацию, и вы можете рассчитать налог на выплату, налог на вычет, и он может сериализоваться, сохраняться на диске и т. Д.
И теперь у меня есть один и тот же объект, разделенный на множество и более мелкие классы, которые имеют преимущество в том, что их можно гораздо проще и лучше тестировать модульно, но не дают хорошего обзора всей бизнес-модели.
И да, я знаю, что вы не хотите связывать свои объекты с базой данных, но вы можете добавить хранилище в большой класс ипотеки, чтобы решить эту проблему.
Кроме того, не всегда легко найти хорошие имена для классов, которые делают только крошечные вещи.
Так что я не уверен, что буква «S» всегда хорошая идея.
источник