Когда вы должны и не должны использовать ключевое слово «новый»?

15

Я смотрел презентацию Google Tech Talk по модульному тестированию , которую проводил Миско Хевери, и он сказал, что следует избегать использования newключевого слова в коде бизнес-логики.

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

Мне интересно, сделал ли я что-то не так, когда использовал новое ключевое слово для своей программы. И где мы можем нарушить это «правило»?

Сэл
источник
2
Можете ли вы добавить тег, связанный с языком программирования? Новое существует во многих языках (C ++, Java, Ruby и многие другие) и имеет различную семантику.
Сакиск

Ответы:

16

Это больше руководство, чем жесткое правило.

Используя «new» в своем производственном коде, вы связываете свой класс с его соавторами. Если кто-то хочет использовать какого-то другого соавтора, например какого-то фиктивного соавтора, для модульного тестирования, он не может - потому что соавтор создан в вашей бизнес-логике.

Конечно, кому-то нужно создавать эти новые объекты, но это часто лучше оставить в одном из двух мест: инфраструктуре внедрения зависимостей, такой как Spring, или в любом другом классе, который создает экземпляр вашего класса бизнес-логики, внедренного через конструктор.

Конечно, вы можете зайти слишком далеко. Если вы хотите вернуть новый ArrayList, то вы, вероятно, в порядке - особенно если это будет неизменный список.

Главный вопрос, который вы должны задать себе: «Является ли основная ответственность этого фрагмента кода созданием объектов этого типа или это просто деталь реализации, которую я мог бы разумно переместить куда-нибудь еще?»

Билл Мичелл
источник
1
Ну, если вы хотите, чтобы это был неизменный список, вы должны использовать Collections.unmodifiableListчто-то. Но я знаю, что вы имеете в виду :)
MatrixFrog
Да, но вам нужно каким-то образом создать исходный список, который вы затем преобразуете в неизменяемый ...
Билл Мичелл
5

Суть этого вопроса в конце: мне интересно, сделал ли я что-то не так, когда использовал новое ключевое слово для своей программы. И где мы можем нарушить это «правило»?

Если вы можете написать эффективные модульные тесты для своего кода, то вы не ошиблись. Если ваше использование newзатрудняет или делает невозможным модульное тестирование кода, вам следует пересмотреть использование нового кода. Вы можете распространить этот анализ на взаимодействие с другими классами, но способность писать надежные модульные тесты часто является достаточно хорошим прокси.

ObscureRobot
источник
5

Короче говоря, всякий раз, когда вы используете «new», вы тесно связываете класс, содержащий этот код, с создаваемым объектом; чтобы создать экземпляр одного из этих объектов, класс, выполняющий создание экземпляра, должен знать о конкретном классе, который создается. Итак, при использовании «new» вы должны подумать, является ли класс, в который вы помещаете экземпляр, «хорошим» местом для этих знаний, и вы готовы внести изменения в эту область, если форма объект должен был измениться.

Тесной связи, то есть объекта, обладающего знаниями другого конкретного класса, не всегда следует избегать; на каком-то уровне что-то, КУДА-ТО, должно знать, как создать этот объект, даже если все остальное имеет дело с объектом, получив его копию откуда-то еще. Однако, когда создаваемый класс изменяется, любой класс, который знает о конкретной реализации этого класса, должен быть обновлен, чтобы правильно обрабатывать изменения этого класса.

Вопрос, который вы всегда должны задавать: «Будет ли этот класс знать, как создать этот другой класс, как ответственность при обслуживании приложения?» Обе основные методологии проектирования (SOLID и GRASP) обычно отвечают «да» по несколько разным причинам. Тем не менее, они являются только методологиями, и оба имеют крайнее ограничение, что они не были сформулированы на основе знания вашей уникальной программы. Таким образом, они могут ошибиться только на стороне предостережения и предположить, что любая точка жесткой связи ВСЕГДА вызовет у вас проблему, связанную с внесением изменений в одну или обе стороны этого пункта. Вы должны принять окончательное решение, зная три вещи; лучшая теоретическая практика (которая заключается в том, чтобы все объединить, потому что все может измениться); стоимость внедрения теоретической передовой практики (которая может включать в себя несколько новых уровней абстракции, которые облегчат один тип изменений, мешая другому); и реальная вероятность того, что тип изменений, которые вы ожидаете, когда-либо будет необходим.

Некоторые общие рекомендации:

  • Избегайте тесной связи между библиотеками скомпилированного кода. Интерфейс между DLL (или EXE и его DLL) является основным местом, где тесная связь будет представлять собой недостаток. Если вы вносите изменения в класс A в DLL X, а класс B в основном EXE-файле знает о классе A, вам придется перекомпилировать и выпустить оба двоичных файла. В пределах одного двоичного файла более жесткое соединение обычно более допустимо, поскольку весь двоичный файл должен быть перестроен для любого изменения в любом случае. Иногда необходимость перестраивать несколько двоичных файлов неизбежна, но вы должны структурировать свой код таким образом, чтобы избежать его там, где это возможно, особенно в ситуациях, когда пропускная способность слишком высока (например, развертывание мобильных приложений; добавление новой библиотеки DLL в обновление намного дешевле). чем пихать всю программу).

  • Избегайте тесной связи между основными «логическими центрами» вашей программы. Вы можете думать о хорошо структурированной программе, состоящей из горизонтальных и вертикальных срезов. Горизонтальные срезы могут быть традиционными уровнями приложения, такими как пользовательский интерфейс, контроллер, домен, DAO, данные; вертикальные срезы могут быть определены для отдельных окон или представлений, или для отдельных «пользовательских историй» (например, создание новой записи некоторого базового типа). При выполнении вызова, который перемещается вверх, вниз, влево или вправо в хорошо структурированной системе, вы должны, как правило, абстрагировать указанный вызов. Например, когда валидация требует извлечения данных, она не должна иметь прямого доступа к БД, а должна вызывать интерфейс для извлечения данных, который поддерживается фактическим объектом, который знает, как это сделать. Когда некоторый элемент управления пользовательского интерфейса должен выполнить расширенную логику, включающую другое окно он должен абстрагироваться от запуска этой логики через событие и / или обратный вызов; ему не нужно знать, что будет сделано в результате, что позволит вам изменить то, что будет сделано, без изменения элемента управления, который его запускает.

  • В любом случае, подумайте, насколько легко или сложно будет сделать изменение, и насколько вероятно будет упомянутое изменение. Если объект, который вы создаете, когда-либо использовался только из одного места, и вы не предвидите, что это изменение, то жесткая связь, как правило, более допустима и может даже превзойти в этой ситуации слабую связь. Слабая связь требует абстракции, которая является дополнительным уровнем, который предотвращает изменение зависимых объектов, когда реализация зависимости должна измениться. Однако если сам интерфейс должен измениться (добавление нового вызова метода или добавление параметра к существующему вызову метода), то интерфейс фактически увеличивает объем работы, необходимый для внесения изменения. Вы должны взвесить вероятность различных типов изменений, влияющих на дизайн,

Keiths
источник
3

Объекты, которые содержат только данные, или DTO (объекты передачи данных) хороши, создают их весь день. Если вы хотите избежать использования newклассов, которые выполняют действия, вы хотите, чтобы классы-потребители вместо этого программировали для интерфейса , где они могут вызывать эту логику и использовать ее, но не должны нести ответственность за фактическое создание экземпляров классов, которые содержат логику. , Классы, выполняющие действия или содержащие логику, являются зависимостями. Доклад Миско направлен на то, чтобы вы внедрили эти зависимости и позволили какой-то другой организации быть ответственной за их создание.

Энтони Пеграм
источник
2

Другие уже упоминали об этом, но хотели процитировать это из Чистого кода дяди Боба (Боба Мартина), потому что это может облегчить понимание концепции:

«Мощным механизмом отделения конструкции от использования является Dependency Injection (DI), применение Inversion of Control (IoC) к управлению зависимостями. Inversion of Control перемещает вторичные обязанности от объекта к другим объектам, которые предназначены для этой цели, тем самым поддерживая Single ответственность Принцип . В контексте управления зависимостями, объект не должен брать на себя ответственность за инстанцирование зависимости самого. Вместо этого он должен передать эту ответственность другому «авторитетному» механизму, тем самым переворачивая контроль. Поскольку установка является глобальной проблемой, это Авторитетным механизмом обычно будет «основная» программа или контейнер специального назначения ».

Я думаю, что всегда полезно следовать этому правилу. Другие упоминали о более важном IMO -> он отделяет ваш класс от его зависимостей (соавторов). Вторая причина заключается в том, что он делает ваши классы меньше и толще, легче для понимания и даже повторного использования в других контекстах.

c_maker
источник