Иногда мне попадаются методы с неудобным количеством параметров. Чаще всего они кажутся конструкторами. Кажется, должен быть способ получше, но я не понимаю, что это такое.
return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
Я думал об использовании структур для представления списка параметров, но это, кажется, просто переносит проблему из одного места в другое и создает в процессе другой тип.
ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);
Так что это не похоже на улучшение. Итак, каков лучший подход?
refactoring
рекурсивный
источник
источник
Ответы:
Лучшим способом было бы найти способы сгруппировать аргументы вместе. Это предполагает и действительно работает только в том случае, если вы получите несколько «группировок» аргументов.
Например, если вы передаете спецификацию прямоугольника, вы можете передать x, y, ширину и высоту, или вы можете просто передать объект прямоугольника, который содержит x, y, ширину и высоту.
Ищите подобные вещи при рефакторинге, чтобы немного его очистить. Если аргументы действительно не могут быть объединены, начните искать, есть ли у вас нарушение принципа единой ответственности.
источник
Я предполагаю, что вы имеете в виду C # . Некоторые из этих вещей применимы и к другим языкам.
У вас есть несколько вариантов:
переключиться с конструктора на средства задания свойств . Это может сделать код более читабельным, потому что читателю очевидно, какое значение каким параметрам соответствует. Синтаксис инициализатора объекта делает это красивым. Это также просто реализовать, поскольку вы можете просто использовать автоматически сгенерированные свойства и пропустить написание конструкторов.
Однако вы теряете неизменность и теряете возможность гарантировать, что требуемые значения установлены перед использованием объекта во время компиляции.
Строитель шаблон .
Подумайте об отношениях между
string
иStringBuilder
. Вы можете получить это для своих собственных классов. Мне нравится реализовывать его как вложенный класс, поэтому у классаC
есть связанный классC.Builder
. Мне также нравится свободный интерфейс в конструкторе. Если все сделано правильно, можно получить такой синтаксис:У меня есть сценарий PowerShell, который позволяет мне сгенерировать код компоновщика для всего этого, где ввод выглядит так:
Так что я могу сгенерировать во время компиляции.
partial
классы позволяют мне расширять как основной класс, так и конструктор без изменения сгенерированного кода.Рефакторинг "Введение в объект параметров" . См. Каталог рефакторинга . Идея состоит в том, что вы берете некоторые из передаваемых параметров и помещаете их в новый тип, а затем вместо этого передаете экземпляр этого типа. Если вы сделаете это, не задумываясь, вы вернетесь туда, откуда начали:
становится
Однако этот подход имеет наибольший потенциал для положительного влияния на ваш код. Итак, продолжайте, следуя этим шагам:
Ищите подмножества параметров, которые имеют смысл вместе. Простое бездумное группирование всех параметров функции вместе мало что дает; цель состоит в том, чтобы группировки имели смысл. Вы поймете, что правильно поняли, когда название нового типа станет очевидным.
Найдите другие места, где эти значения используются вместе, и используйте там новый тип. Скорее всего, когда вы нашли хороший новый тип для набора значений, который вы уже используете повсюду, этот новый тип будет иметь смысл и во всех этих местах.
Ищите функциональность, которая есть в существующем коде, но принадлежит новому типу.
Например, возможно, вы видите код, который выглядит так:
Можно взять
minSpeed
иmaxSpeed
параметры и поместить их в новом типе:Это лучше, но чтобы действительно воспользоваться преимуществами нового типа, переместите сравнения в новый тип:
И вот мы кое-что достигли: реализация
SpeedIsAcceptable()
now говорит о том, что вы имеете в виду, и у вас есть полезный, многоразовый класс. (Следующий очевидный шаг должен сделать ,SpeedRange
чтобыRange<Speed>
.)Как видите, Introduce Parameter Object было хорошим началом, но его реальная ценность заключалась в том, что он помог нам обнаружить полезный тип, который отсутствовал в нашей модели.
источник
Если это конструктор, особенно если есть несколько перегруженных вариантов, вам следует взглянуть на шаблон Builder:
Если это обычный метод, вам следует подумать о взаимосвязях между передаваемыми значениями и, возможно, создать объект передачи.
источник
Классический ответ на этот вопрос - использовать класс для инкапсуляции некоторых или всех параметров. Теоретически это звучит великолепно, но я из тех, кто создает классы для концепций, имеющих значение в предметной области, поэтому применить этот совет не всегда легко.
Например, вместо:
Вы могли бы использовать
YMMV
источник
Это цитата из книги Фаулера и Бека: «Рефакторинг».
источник
Я не хочу показаться мудрым, но вы также должны убедиться, что данные, которые вы передаете, действительно должны передаваться: передача материала конструктору (или методу в этом отношении) немного похожа на мало внимания уделяется поведению объекта.
Не поймите меня неправильно: методы и конструкторы будут иметь много параметров иногда. Но когда вы столкнетесь с этим, попробуйте вместо этого рассмотреть возможность инкапсуляции данных с поведением .
Такой запах (раз уж мы говорим о рефакторинге, это ужасное слово кажется подходящим ...) также может быть обнаружен для объектов, у которых много (читай: любых) свойств или геттеров / сеттеров.
источник
Когда я вижу длинные списки параметров, мой первый вопрос: не слишком ли много делает эта функция или объект. Рассматривать:
Конечно, этот пример умышленно смешон, но я видел множество реальных программ с примерами, лишь немного менее нелепыми, где один класс используется для хранения множества едва связанных или несвязанных вещей, по-видимому, только потому, что одной и той же вызывающей программе нужны оба или потому, что программист думал об обоих одновременно. Иногда простое решение - просто разбить класс на несколько частей, каждая из которых выполняет свои функции.
Чуть сложнее, когда классу действительно нужно иметь дело с несколькими логическими вещами, такими как заказ клиента и общая информация о клиенте. В этих случаях создайте класс для клиента и класс для заказа и позвольте им разговаривать друг с другом по мере необходимости. Так что вместо:
У нас могло быть:
Хотя, конечно, я предпочитаю функции, которые принимают всего 1, 2 или 3 параметра, иногда мы должны признать, что на самом деле эта функция занимает много, и что количество само по себе не создает сложности. Например:
Да, это набор полей, но, вероятно, все, что мы собираемся с ними делать, - это сохранять их в запись базы данных или выводить на экран или что-то в этом роде. Здесь действительно не так много обработки.
Когда мои списки параметров становятся длинными, я предпочитаю, чтобы я мог дать полям разные типы данных. Например, когда я вижу такую функцию, как:
И затем я вижу, что он вызван:
Я забеспокоился. Глядя на звонок, совсем не понятно, что означают все эти загадочные числа, коды и флаги. Это просто просьба об ошибках. Программист может легко запутаться в порядке следования параметров и случайно переключить два, и если они одного типа данных, компилятор просто примет его. Я бы предпочел иметь подпись, в которой все эти вещи являются перечислениями, поэтому вызов передает такие вещи, как Type.ACTIVE вместо «A» и CreditWatch.NO вместо «false» и т. Д.
источник
Если некоторые из параметров конструктора являются необязательными, имеет смысл использовать построитель, который получит требуемые параметры в конструкторе и будет иметь методы для необязательных параметров, возвращающих построитель, которые будут использоваться следующим образом:
Подробности этого описаны в Эффективной Java, 2-е изд., Стр. 11. Что касается аргументов метода, в той же книге (стр. 189) описаны три подхода к сокращению списков параметров:
DinoDonkey
вместоdino
иdonkey
источник
Я бы использовал конструктор по умолчанию и установщики свойств. В C # 3.0 есть хороший синтаксис, позволяющий делать это автоматически.
Улучшение кода заключается в упрощении конструктора и отсутствии необходимости поддерживать несколько методов для поддержки различных комбинаций. «Вызывающий» синтаксис все еще немного «многословен», но не хуже, чем вызов установщиков свойств вручную.
источник
Вы не предоставили достаточно информации, чтобы дать хороший ответ. Длинный список параметров не является плохим по своей сути.
можно интерпретировать как:
В этом случае вам будет гораздо лучше создать класс для инкапсуляции параметров, потому что вы придаете значение различным параметрам таким образом, чтобы компилятор мог их проверить, а также визуально облегчая чтение кода. Это также упрощает чтение и последующий рефакторинг.
В качестве альтернативы, если у вас были:
Это совсем другой случай, потому что все объекты разные (и вряд ли они будут перепутаны). Согласились, что если все объекты необходимы, и все они разные, нет смысла создавать класс параметров.
Кроме того, некоторые параметры необязательны? Существуют ли переопределения метода (одно и то же имя метода, но разные сигнатуры методов?) Все эти виды деталей имеют значение в отношении того, какой будет лучший ответ.
* Пакет свойств также может быть полезен, но не лучше, учитывая отсутствие предыстории.
Как видите, на этот вопрос существует более одного правильного ответа. Сделайте ваш выбор.
источник
Вы можете попытаться сгруппировать свой параметр в несколько значимых структур / классов (если возможно).
источник
Я обычно склоняюсь к подходу структур - по-видимому, большинство этих параметров каким-то образом связаны и представляют состояние некоторого элемента, имеющего отношение к вашему методу.
Если набор параметров не может быть преобразован в значимый объект, это, вероятно, признак того, что
Shniz
делается слишком много, и рефакторинг должен включать разбиение метода на отдельные задачи.источник
Вы можете обменять сложность на строки исходного кода. Если сам метод делает слишком много (швейцарский нож), попробуйте сократить вдвое его задачи, создав другой метод. Если метод прост, только для него требуется слишком много параметров, тогда можно использовать так называемые объекты параметров.
источник
Если ваш язык поддерживает это, используйте именованные параметры и сделайте как можно больше необязательных (с разумными значениями по умолчанию).
источник
Я думаю, что метод, который вы описали, - это правильный путь. Когда я нахожу метод с большим количеством параметров и / или метод, который, вероятно, потребует больше в будущем, я обычно создаю объект ShnizParams для передачи, как вы описываете.
источник
Как насчет того, чтобы не устанавливать его сразу в конструкторах, а делать это через свойства / сеттеры ? Я видел некоторые классы .NET, которые используют этот подход, например
Process
class:источник
Я согласен с подходом к перемещению параметров в объект параметра (структуру). Вместо того, чтобы просто объединять их все в один объект, проверьте, используют ли другие функции аналогичные группы параметров. Объект paramater более ценен, если он используется с несколькими функциями, где вы ожидаете, что этот набор параметров будет постоянно меняться в этих функциях. Возможно, вы поместите только некоторые параметры в новый объект параметра.
источник
Если у вас так много параметров, велика вероятность, что метод слишком много работает, поэтому сначала решите эту проблему, разделив метод на несколько более мелких. Если после этого у вас все еще остается слишком много параметров, попробуйте сгруппировать аргументы или превратить некоторые параметры в члены экземпляра.
Предпочитайте маленькие классы / методы большим. Помните принцип единственной ответственности.
источник
Именованные аргументы - хороший вариант (при условии, что язык их поддерживает) для устранения неоднозначности длинных (или даже коротких!) Списков параметров, а также позволяет (в случае конструкторов) неизменяемость свойств класса без наложения требования о его существовании. в частично построенном состоянии.
Другой вариант, который я бы искал при выполнении такого рода рефакторинга, - это группы связанных параметров, которые лучше обрабатывать как независимый объект. Используя класс Rectangle из более раннего ответа в качестве примера, конструктор, который принимает параметры для x, y, высоты и ширины, может вынести x и y в объект Point, что позволит вам передать три параметра конструктору Rectangle. Или пойти немного дальше и сделать его двумя параметрами (UpperLeftPoint, LowerRightPoint), но это будет более радикальный рефакторинг.
источник
Это зависит от того, какие аргументы у вас есть, но если они содержат много логических значений / параметров, возможно, вы могли бы использовать Flag Enum?
источник
Я думаю, что эта проблема глубоко связана с областью проблемы, которую вы пытаетесь решить с помощью класса.
В некоторых случаях конструктор с 7 параметрами может указывать на неправильную иерархию классов: в этом случае вспомогательная структура / класс, предложенная выше, обычно является хорошим подходом, но тогда вы также можете получить множество структур, которые являются просто мешками свойств и не делай ничего полезного. Конструктор с 8 аргументами также может указывать на то, что ваш класс слишком универсален / слишком универсален, поэтому ему нужно много параметров, чтобы быть действительно полезным. В этом случае вы можете либо провести рефакторинг класса, либо реализовать статические конструкторы, которые скрывают реальные сложные конструкторы: например. Shniz.NewBaz (foo, bar) действительно может вызывать реальный конструктор, передавая правильные параметры.
источник
Одно из соображений заключается в том, какое из значений будет доступно только для чтения после создания объекта?
Открытые для записи свойства, возможно, могут быть назначены после создания.
Откуда в конечном итоге берутся ценности? Возможно, некоторые значения действительно являются внешними, тогда как другие действительно взяты из некоторой конфигурации или глобальных данных, которые поддерживаются библиотекой.
В этом случае вы можете скрыть конструктор от внешнего использования и предоставить для него функцию Create. Функция create принимает истинно внешние значения и создает объект, а затем использует средства доступа, доступные только для библиотеки, для завершения создания объекта.
Было бы действительно странно иметь объект, который требует 7 или более параметров, чтобы дать объекту полное состояние, и все они действительно являются внешними по своей природе.
источник
Когда в классе есть конструктор, который принимает слишком много аргументов, это обычно признак того, что у него слишком много обязанностей. Вероятно, его можно разбить на отдельные классы, которые взаимодействуют для предоставления одинаковых функций.
Если вам действительно нужно столько аргументов для конструктора, вам может помочь шаблон Builder. Цель состоит в том, чтобы по-прежнему передавать все аргументы конструктору, чтобы его состояние было инициализировано с самого начала, и вы все равно можете сделать класс неизменным, если это необходимо.
Увидеть ниже :
источник