Можно ли иметь класс только со свойствами для целей рефакторинга?

79

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

Xaisoft
источник
Ответы ниже хороши. Я всегда создавал для этого класс. Вы используете автоматические свойства? msdn.microsoft.com/en-us/library/bb384054.aspx
Харв,
Иначе известный как тип POD (простые старые данные).
new123456
17
Хотя многие из приведенных ответов хороши, и действительно хорошая идея реорганизовать эти параметры во что-то более простое в обращении, я бы сказал, что тот факт, что этому методу требуется 30 отдельных частей данных для выполнения своей задачи, является признаком что он делает слишком много. Но опять же, мы не видели рассматриваемого метода и даже не знали о его назначении.
пользователь
1
Итак, этот метод, который принимает все параметры, вы переместили его в новый класс? Или старый метод просто принимает новый класс как единственный параметр? Я бы посмотрел на то, чтобы поместить метод в новый класс, если это имеет смысл. Классы только с данными и без методов называются анемичными классами, и это кажется немного менее объектно-ориентированным. Но это не жесткое правило, просто на что посмотреть.
Курт
@ Майкл: Я согласен с тобой. Еще один момент - я считаю, что вам не следует создавать объект, выделенный для метода, если его внутренняя часть не имеет отношения вне метода.
Saturn Technologies

Ответы:

75

Это отличная идея. Обычно так заключаются контракты данных, например, в WCF.

Одно из преимуществ этой модели состоит в том, что если вы добавляете новый параметр, потребителю класса не нужно изменять только для добавления параметра.

Как упоминает Дэвид Хеффернан, это может помочь самостоятельно задокументировать код:

FrobRequest frobRequest = new FrobRequest
{
    FrobTarget = "Joe",
    Url = new Uri("http://example.com"),
    Count = 42,
};
FrobResult frobResult = Frob(frobRequest);
Маккей
источник
Отлично. У меня создалось впечатление, что просто иметь класс со свойствами и без методов бесполезно.
Xaisoft
2
Если он будет содержать только элементы данных, я предпочитаю сделать его структурным. Использование struct создает впечатление, что это просто данные.
Yousf
28
использование структуры имеет побочные эффекты, такие как копирование при передаче семантики. Пожалуйста, убедитесь, что вы знаете об этом, прежде чем принимать такое решение
Адриан Занеску
Так обычно пишутся модули javascript, и кажется, что это лучший метод для принятия большего или меньшего количества параметров по мере развития модуля. someFunction ({myParam1: 'что-то', myParam2: 'somethingElse'});
Кристиан
63

Хотя другие ответы здесь правильно указывают на то, что передача экземпляра класса лучше, чем передача 30 параметров, имейте в виду, что большое количество параметров может быть симптомом основной проблемы.

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

Как вариант, ищите способы сгруппировать параметры в объекты более высокого уровня абстракции. Сброс кучи несвязанных параметров в один класс - последнее средство ИМО.

См. Сколько параметров слишком много? для получения дополнительных идей по этому поводу.

Д'Арси Риттич
источник
В каком смысле эти параметры не связаны? Все они используются этим методом. Это очень крепкие отношения. Более того, параметры в стеке, как правило, желательно указывать. Подумайте о многопоточности.
Дэвид Хеффернан
12
На это нельзя ответить, не увидев кода OP, но я не согласен с тем, что просто потому, что два значения используются вместе в методе, они имеют сильную связь. Если бы это было так, объектно-ориентированный дизайн означал бы создание одного большого класса, который будет содержать все возможные свойства, используемые в вашем приложении.
Д'Арси Риттич
Нет, ты прав, не обязательно родственник. Но также не обязательно несвязанные. Итак, как вы говорите, для уверенности нужно увидеть код.
Дэвид Хеффернан
23
30 параметров? Я бы согласился, скорее всего, на несвязанный, а также, вероятно, на слишком длинный метод, который имеет высокую цикломатическую сложность и является убежищем для ошибок. Представьте, что мы говорим о методе, поведение которого имеет как минимум 30 измерений, в которых оно может варьироваться. Если есть модульные тесты для этого метода, я бы не хотел писать их TBH.
Стив Роуботэм
3
Другой вероятный сценарий - все параметры связаны, но плохо организованы. Вполне вероятно, что группы параметров следует объединять в отдельные объекты, а не передавать по частям. Скажем, у меня есть метод, который работает с такой информацией, как «человек», «адрес», «предписание» ... который легко мог бы насчитывать 30 параметров, если бы каждый из дискретных фрагментов информации был передан в мой метод отдельно.
Nate CK
25

Хорошее начало. Но теперь, когда у вас есть этот новый класс, подумайте о том, чтобы вывернуть свой код наизнанку. Переместите метод, который принимает этот класс в качестве параметра, в ваш новый класс (конечно, передав экземпляр исходного класса в качестве параметра). Теперь у вас есть большой метод, единственный в классе, и его будет легче разделить на более мелкие, более управляемые и тестируемые методы. Некоторые из этих методов могут вернуться в исходный класс, но значительная часть, вероятно, останется в вашем новом классе. Вы перешли от « Ввести объект параметра» к « Заменить метод» на объект-метод .

Наличие метода с тридцатью параметрами - довольно сильный признак того, что метод слишком длинный и слишком сложный. Слишком сложно отлаживать, слишком сложно тестировать. Так что вы должны что-то с этим сделать, и Introduce Parameter Object - отличное место для начала.

Карл Манастер
источник
4
Это АБСОЛЮТНО ВАЖНО! Создание этих наборов атрибутов замечательно только потому, что это первый шаг к созданию новых классов с собственными методами, и вы почти всегда найдете методы, которые принадлежат вашим новым классам - ищите их! Я думаю, что это самый критический ответ в этой группе, он должен быть в верхней части.
Bill K
15

Хотя рефакторинг объекта параметров сам по себе не является плохой идеей, его не следует использовать, чтобы скрыть проблему, заключающуюся в том, что класс, которому требуется 30 фрагментов данных, предоставленных из других источников, все еще может быть чем-то вроде запаха кода. Рефакторинг объекта «Введение параметров», вероятно, следует рассматривать как этап в более широком процессе рефакторинга, а не как его завершение.

Одна из проблем, которую он на самом деле не решает, - это Feature Envy. Не указывает ли тот факт, что класс, которому передается объект параметров, так интересны данные другого класса, на то, что, возможно, методы, которые работают с этими данными, должны быть перемещены туда, где они находятся? Действительно лучше идентифицировать кластеры методов и данных, которые принадлежат друг другу, и группировать их в классы, тем самым увеличивая инкапсуляцию и делая ваш код более гибким.

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

Стив Роуботэм
источник
10

Это отличная идея и очень распространенное решение проблемы. Методы с более чем 2 или 3 параметрами становятся экспоненциально сложнее и труднее для понимания.

Инкапсуляция всего этого в одном классе делает код более понятным. Поскольку у ваших свойств есть имена, вы можете написать самодокументированный код следующим образом:

params.height = 42;
params.width = 666;
obj.doSomething(params);

Естественно, когда у вас много параметров, альтернатива, основанная на позиционной идентификации, просто ужасна.

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

Дэвид Хеффернан
источник
9

Мартин Фаулер в своей книге « Рефакторинг» называет этот объект « ввести параметр» . С такой цитатой мало кто назовет это плохой идеей.

Остин Салонен
источник
1
Да, но для 30 параметров что-то не так в другом месте, что нужно исправить в первую очередь
manojlds
5

30 параметров - это бардак. Я думаю, что лучше иметь класс со свойствами. Вы даже можете создать несколько «классов параметров» для групп параметров, которые входят в одну категорию.

К. Эвенхейс
источник
3

Вы также можете рассмотреть возможность использования структуры вместо класса.

Но то, что вы пытаетесь сделать, очень распространено и является отличной идеей!

Тэд Донахе
источник
Я действительно думал об использовании структуры, но мне было любопытно, будут ли какие-либо недостатки, если я использую структуру.
Xaisoft
Почему Дэвид? При чем тут количество полей? Просто любопытно.
Tad Donaghe
2
Что касается производительности. Хотя семантически имеет больше смысла использовать структуру здесь, так как только значение каждого поля имеет значение, а ссылочная идентификация не имеет значения, 30 или около того полей - это больше для копирования (скажем, они в основном ссылочные типы; 120 байтов для 32-битных и 240 байтов для 64), чем с классом (4 или 8 байтов). Однако природа структур с копированием по значению означает, что после копирования доступ будет быстрее, чем со ссылочным типом, поэтому порог, когда структура менее эффективна, выше, чем размер с 1 указателем, который влечет за собой выше. При> 4 размерах указателя самое время рассмотреть это.
Джон Ханна
Потрясающе. Спасибо за объяснение! :)
Tad Donaghe
3

Может быть разумным использовать класс Plain Old Data независимо от того, проводите ли вы рефакторинг или нет. Мне любопытно, почему вы подумали, что это может быть не так.

Джон Ханна
источник
Я точно не помню, но думаю, что когда я недавно где-то упомянул, что создавал класс только с свойствами, тогда на него смотрели свысока. Что касается вашего второго пункта, вы говорите, что в метод должны передаваться только параметры, которые не изменяются, другими словами, если метод изменяет параметр, он не должен передаваться в качестве параметра.
Xaisoft
Классы, которые являются только свойствами, могут быть неприятным запахом (см. Ответ Стива Роуботэма), который указывает на то, что вместо этого должен быть «полным» классом. Хороший признак - если вы в конечном итоге будете использовать похожие классы более одного раза. Однако это не всегда так, и при подбрасывании между классом с 30 полями и методом с 30 параметрами есть хороший призыв к первому. Кроме того, это может стать началом построения того «полного» класса.
Джон Ханна
... Я удаляю свой комментарий о неизменности, поскольку я больше думал о том, что это только общедоступный метод (где объект «запрос» описывает намерение вызывающего). Здесь изменение «запроса» может привести к путанице. В единичном случае удобнее изменять и строить по мере продвижения. Неизменяемость означала бы просто замену вызова с 30 аргументами конструктором с 30 аргументами, за которым следует вызов, так что выигрыша не будет.
Джон Ханна
3

Может быть, необязательные и именованные параметры C # 4.0 станут хорошей альтернативой этому?

В любом случае, метод, который вы описываете, также может быть полезен для абстрагирования поведения программ. Например, у вас может быть одна стандартная SaveImage(ImageSaveParameters saveParams)функция в интерфейсе, которая ImageSaveParametersтакже является интерфейсом и может иметь дополнительные параметры в зависимости от формата изображения. Например JpegSaveParametersесть Quality-свойство пока PngSaveParametersимеет BitDepth-свойство.

Вот как это делает диалог сохранения в Paint.NET, так что это очень реальный пример.

тряпка
источник
3

Как указывалось ранее: это правильный шаг, но примите во внимание и следующее:

  • ваш метод может быть слишком сложным (вам следует подумать о том, чтобы разделить его на несколько методов или даже превратить его в отдельный класс)
  • если вы создаете класс для параметров, сделайте его неизменным
  • если многие параметры могут иметь значение null или иметь какое-то значение по умолчанию, вы можете использовать шаблон построителя для своего класса.
Получить
источник
0

Здесь так много отличных ответов. Я хочу добавить свои два цента.

Объект-параметр - хорошее начало. Но можно сделать еще больше. Рассмотрим следующее (примеры рубинов):

/ 1 / Вместо того, чтобы просто группировать все параметры, посмотрите, может ли быть значимая группировка параметров. Вам может понадобиться более одного объекта параметра.

def display_line(startPoint, endPoint, option1, option2)

может стать

def display_line(line, display_options)

/ 2 / Объект параметра может иметь меньшее количество свойств, чем исходное количество параметров.

def double_click?(cursor_location1, control1, cursor_location2, control2)

может стать

def double_click?(first_click_info, second_click_info) 
                       # MouseClickInfo being the parameter object type 
                       # having cursor_location and control_at_click as properties

Такое использование поможет вам обнаружить возможности добавления значимого поведения к этим объектам параметров. Вы обнаружите, что они избавляются от своего первоначального запаха Data Class раньше, к вашему удобству. : -)

Рпаттаби
источник