Хотя это и общий вопрос, моя сфера - скорее C #, так как я знаю, что языки, подобные C ++, имеют различную семантику в отношении выполнения конструктора, управления памятью, неопределенного поведения и т. Д.
Кто-то задал мне интересный вопрос, на который мне было нелегко ответить.
Почему (или это вообще?) Считается плохим проектом, позволяющим конструктору класса запускать бесконечный цикл (т. Е. Игровой цикл)?
Есть некоторые концепции, которые нарушаются этим:
- Как и принцип наименьшего удивления, пользователь не ожидает, что конструктор будет вести себя так.
- Модульные тесты сложнее, так как вы не можете создать этот класс или внедрить его, так как он никогда не выходит из цикла.
- Конец цикла (конец игры) - это концептуально время окончания конструктора, что тоже странно.
- Технически такой класс не имеет открытых членов, кроме конструктора, что усложняет понимание (особенно для языков, где реализация недоступна)
И тут возникают технические проблемы:
- Конструктор фактически никогда не заканчивается, так что же здесь происходит с GC? Этот объект уже в Gen 0?
- Вывод из такого класса невозможен или, по крайней мере, очень сложен из-за того, что базовый конструктор никогда не возвращается
Есть ли что-то более очевидно плохое или коварное с таким подходом?
c#
architecture
Самуил
источник
источник
var g = new Game {...}; g.MainLoop();
while(true)
петлю в собственность сеттер:new Game().RunNow = true
?Ответы:
Какова цель конструктора? Возвращает вновь созданный объект. Что делает бесконечный цикл? Это никогда не возвращается. Как конструктор может вернуть вновь созданный объект, если он вообще не возвращается? Не может
Следовательно, бесконечный цикл нарушает основной контракт конструктора: что-то строить.
источник
Да, конечно. Это ненужное, неожиданное, бесполезное, нелегальное. Это нарушает современные концепции классового дизайна (сплоченность, сцепление). Он нарушает контракт метода (конструктор имеет задание, а не просто какой-то случайный метод). Это, конечно, плохо поддерживается, будущие программисты будут тратить много времени, пытаясь понять, что происходит, и пытаясь угадать причины, почему это было сделано таким образом.
Ничто из этого не является «ошибкой» в том смысле, что ваш код не работает. Но в долгосрочной перспективе это, вероятно, повлечет за собой огромные вторичные затраты (по сравнению с первоначальной стоимостью написания кода), затрудняя сопровождение кода (т. Е. Сложность добавления тестов, сложность повторного использования, сложность отладки, сложность расширения и т. Д.). .).
Многие / самые современные улучшения в методах разработки программного обеспечения сделаны специально для того, чтобы упростить процесс написания / тестирования / отладки / обслуживания программного обеспечения. Все это обходится подобными вещами, где код размещается случайным образом, потому что он «работает».
К сожалению, вы регулярно встречаетесь с программистами, которые совершенно не знают всего этого. Это работает, вот и все.
Чтобы закончить с аналогией (другой язык программирования, здесь под рукой стоит задача расчета
2+2
):Что не так с первым подходом? Возвращает 4 через достаточно короткий промежуток времени; верно. Возражения (вместе со всеми обычными причинами, по которым разработчик может их опровергнуть):
bash
(но он никогда не будет работать на Windows, Mac или Android в любом случае)источник
fundamental contract of a constructor: to construct something
на месте.В своем вопросе вы приводите достаточное количество причин, чтобы исключить такой подход, но на самом деле здесь возникает вопрос: «Есть ли что-то более очевидно плохое или изощренное при таком подходе?»
Сначала я подумал, что это бессмысленно. Если ваш конструктор никогда не завершает свою работу, никакая другая часть программы не может получить ссылку на созданный объект, так что логика заключается в том, чтобы поместить его в конструктор вместо обычного метода. Тогда мне пришло в голову, что единственное отличие будет в том, что в вашем цикле вы можете разрешить ссылки на частично сконструированный
this
выход из цикла. Хотя это строго не ограничивается этой ситуацией, гарантируется, что если вы разрешитеthis
экранирование, эти ссылки всегда будут указывать на объект, который не был полностью создан.Я не знаю, хорошо ли определена семантика вокруг такого рода ситуаций в C #, но я бы сказал, что это не имеет значения, потому что большинство разработчиков не захотят в них вникать.
источник
this
в конструкторе возможна, но только если вы находитесь в конструкторе самого производного класса. Если у вас есть два класса,A
иB
сB : A
, если конструкторA
утечекthis
, членыB
будут по-прежнему неинициализированы (и вызовы виртуальных методов или приведение к нимB
могут нанести ущерб, основанный на этом).+1 Для наименьшего удивления, но это сложная концепция, чтобы сформулировать для новых разработчиков. На прагматическом уровне трудно отлаживать исключения, возникающие внутри конструкторов, если объект не инициализируется, он не будет существовать для проверки состояния или регистрации извне этого конструктора.
Если вы чувствуете необходимость сделать такой шаблон кода, используйте вместо этого статические методы в классе.
Существует конструктор, обеспечивающий логику инициализации, когда объект создается из определения класса. Вы создаете этот экземпляр объекта, потому что он представляет собой контейнер, который инкапсулирует набор свойств и функций, которые вы хотите вызывать из остальной части логики вашего приложения.
Если вы не намерены использовать объект, который вы «строите», то какой смысл создавать объект в первую очередь? Наличие цикла while (true) в конструкторе фактически означает, что вы никогда не собираетесь его завершать ...
C # - это очень богатый объектно-ориентированный язык со множеством различных конструкций и парадигм, которые вы можете исследовать, знать свои инструменты и когда их использовать, суть:
источник
В этом нет ничего плохого. Однако, что будет важно, так это вопрос, почему вы решили использовать эту конструкцию. Что вы получили, делая это?
Прежде всего, я не буду говорить об использовании
while(true)
в конструкторе. Существует абсолютно ничего плохого с использованием этого синтаксиса конструктора. Однако концепция «запуска игрового цикла в конструкторе» может вызвать некоторые проблемы.В этих ситуациях очень распространено, чтобы конструктор просто создавал объект и имел функцию, которая вызывает бесконечный цикл. Это дает пользователю вашего кода больше гибкости. В качестве примера проблем, которые могут появиться, вы ограничиваете использование своего объекта. Если кто-то хочет иметь в своем классе объект-член, который является «игровым объектом», он должен быть готов запустить весь игровой цикл в своем конструкторе, потому что он должен создать этот объект. Это может привести к пыткам кодирования, чтобы обойти это. В общем случае вы не можете предварительно выделить свой игровой объект, а затем запустить его позже. Для некоторых программ это не проблема, но есть классы программ, в которых вы хотите иметь возможность выделить все заранее и затем вызвать игровой цикл.
Одним из практических правил в программировании является использование простейшего инструмента для работы. Это не жесткое и быстрое правило, но оно очень полезно. Если вызова функции было бы достаточно, зачем беспокоиться о создании объекта класса? Если я увижу более мощный и сложный инструмент, я предполагаю, что у разработчика была причина для этого, и я начну исследовать, какие странные уловки вы могли бы делать.
Есть случаи, когда это может быть разумным. Могут быть случаи, когда вам действительно интуитивно понятно иметь игровой цикл в конструкторе. В тех случаях, вы бежите с этим! Например, ваш игровой цикл может быть встроен в гораздо большую программу, которая создает классы для воплощения некоторых данных. Если вам нужно запустить игровой цикл, чтобы сгенерировать эти данные, может быть очень разумно запустить игровой цикл в конструкторе. Просто отнеситесь к этому как к особому случаю: было бы правильно сделать это, потому что всеобъемлющая программа делает интуитивно понятным для следующего разработчика понять, что вы сделали и почему.
источник
GameTestResults testResults = runGameTests(tests, configuration);
а неGameTestResults testResults = new GameTestResults(tests, configuration);
.У меня сложилось впечатление, что некоторые понятия перепутались и привели к не очень хорошо сформулированному вопросу.
Конструктор класса может начать новый поток, который «никогда» не закончится (хотя я предпочитаю использовать
while(_KeepRunning)
over,while(true)
потому что я могу где-то установить для логического члена значение false).Вы можете извлечь этот метод создания и запуска потока в его собственную функцию, чтобы разделить конструкцию объекта и фактическое начало его работы - я предпочитаю этот способ, потому что я получаю лучший контроль над доступом к ресурсам.
Вы также можете взглянуть на «Шаблон активного объекта». В конце концов, я предполагаю, что вопрос был нацелен на то, когда начинать поток «Активного объекта», и я предпочитаю разделение конструкции и начало для лучшего контроля.
источник
Ваш объект напоминает мне о запуске задач . Объект задачи имеет конструктор, но это есть и куча статических фабричных методов , таких как:
Task.Run
,Task.Start
иTask.Factory.StartNew
.Это очень похоже на то, что вы пытаетесь сделать, и, скорее всего, это «соглашение». Основная проблема, с которой люди сталкиваются, заключается в том, что такое использование конструктора удивляет их. @ JörgWMittag говорит, что это нарушает фундаментальный контракт , который, я полагаю, означает, что Йорг очень удивлен. Я согласен, и мне больше нечего добавить к этому.
Тем не менее, я хочу предположить, что OP пытается использовать статические фабричные методы. Сюрприз исчезает, и здесь нет принципиального контракта . Люди привыкли к статическим фабричным методам, которые делают особые вещи, и их можно называть соответственно.
Вы можете предоставить конструкторы, которые обеспечивают мелкозернистый контроль (как предлагает @Brandin, что-то вроде того
var g = new Game {...}; g.MainLoop();
, чтобы приспособить пользователей, которые не хотят сразу запускать игру, и, возможно, захотят сначала ее прогнать. И вы можете написать что-то вродеvar runningGame = Game.StartNew();
запуска это сразу легко.источник
Это совсем не плохо.
Зачем тебе это делать? Шутки в сторону. Этот класс имеет одну функцию: конструктор. Если все, что вам нужно, это одна функция, поэтому у нас есть функции, а не конструкторы.
Вы упомянули некоторые очевидные причины против этого сами, но вы упустили самую главную:
Есть лучшие и более простые варианты для достижения того же.
Если есть 2 варианта, и один лучше, то не важно, насколько он лучше, вы выбрали лучший. Кстати, именно поэтому мы
не непочти никогда не использовать GoTo.источник
Целью конструктора является создание вашего объекта. До завершения работы конструктора в принципе небезопасно использовать любые методы этого объекта. Конечно, мы можем вызывать методы объекта внутри конструктора, но затем каждый раз, когда мы делаем это, мы должны убедиться, что это верно для объекта в его текущем полусгоревшем состоянии.
Косвенно вкладывая всю логику в конструктор, вы добавляете больше такого бремени на себя. Это говорит о том, что вы не спроектировали разницу между инициализацией и использованием достаточно хорошо.
источник