Чи нормально мати клас із властивостями лише для цілей рефакторингу?


79

У мене є метод, який приймає 30 параметрів. Я взяв параметри і поставив їх в один клас, щоб я міг просто передати один параметр (клас) у метод. Чи чудово у випадку рефакторингу передавати об’єкт, який інкапсулює всі параметри, навіть якщо це все, що він містить.


Відповіді нижче - хороші. Я для цього завжди створюю клас. Ви використовуєте автоматичні властивості? 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 створює враження, що це лише дані.
Юсф

28
використання struct має побічні ефекти, як копіювання при передачі семантики. Будь ласка, переконайтеся, що ви це знаєте, перш ніж приймати таке рішення
Адріан Занеску

Так зазвичай пишуться модулі javascript, і це, здається, є найкращим методом для прийняття більш-менш параметрів у міру дозрівання модуля. someFunction ({myParam1: 'something', myParam2: 'somethingElse'});
Крістіан

63

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

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

Крім того, шукайте способи групування параметрів у об’єкти вищого рівня абстракції. Злиття групи не пов’язаних між собою параметрів в один клас - це крайній варіант IMO.

Дивіться Скільки параметрів забагато? ще кілька ідей щодо цього.


В якому сенсі ці параметри не пов’язані? Всі вони використовуються цим методом. Це дуже міцні стосунки. Більше того, параметри в стеку, як правило, кращі, ніж державні. Подумайте про багатопоточність.
Девід Хеффернан

12
На це неможливо відповісти, не побачивши код OP, але я не згоден з тим, що просто тому, що два значення використовуються разом у методі, вони мають міцні стосунки. Якби це було правдою, дизайн OO в кінцевому підсумку означав би створення одного великого класу, що містить усі можливі властивості, використані у вашому додатку.
D'Arcy Rittich

Ні, ви маєте рацію, не обов'язково пов'язані між собою. Але також не обов’язково не пов’язані між собою. Отже, як ви кажете, для впевненості потрібно було б побачити код.
Девід Хеффернан

23
30 параметрів? Я б погодився на, ймовірно, непов’язаний, а також, мабуть, свідчить про занадто довгий метод, який має високу цикломатичну складність і є притулком для помилок. Вважайте, що ми говоримо про метод, поведінка якого має щонайменше 30 вимірів, у яких вона може змінюватися. Якщо для цього методу існують модульні тести, я не хотів би, щоб я писав їх TBH.
Steve Rowbotham

3
Інший імовірний сценарій полягає в тому, що всі параметри пов’язані, але вони погано організовані. Цілком ймовірно, що групи параметрів слід об'єднувати в різні об'єкти, а не передавати їх поштучно. Скажімо, у мене є метод, який оперує такою інформацією, як "особа", "адреса", "рецепт" ..., який може легко складати 30 параметрів, якби кожна з дискретних частин інформації передавалася в мій метод окремо.
Nate CK

25

Це хороший початок. Але тепер, коли ви отримали цей новий клас, подумайте про те, щоб перевернути код навиворіт. Перемістіть метод, який приймає цей клас як параметр, у ваш новий клас (звичайно, передаючи екземпляр початкового класу як параметр). Тепер у вас є великий метод, самостійний у класі, і його буде простіше розділити на менші, більш керовані та перевіряються методи. Деякі з цих методів можуть повернутися до початкового класу, але справедлива частина, ймовірно, залишиться у вашому новому класі. Ви перейшли далі " Ввести об'єкт параметра" на " Замінити метод" на "Об'єкт методу" .

Наявність методу з тридцятьма параметрами є досить вагомою ознакою того, що метод занадто довгий і занадто складний. Занадто складно для налагодження, занадто важко для тестування. Отже, вам слід щось з цим зробити, і Introduce Parameter Object - прекрасне місце для початку.


4
Це АБСОЛЮТНО ВАЖЛИВО! Створювати ці набори атрибутів чудово лише тому, що це перший крок до створення нових класів із власними методами, і ви майже завжди знайдете методи, які належать до ваших нових класів - шукайте їх! Я думаю, що це найбільш критична відповідь у цій групі, вона повинна бути вгорі.
Білл К

15

Хоча рефакторинг до об’єкта параметрів сам по собі не є поганою ідеєю, його не слід використовувати, щоб приховати проблему, що клас, який потребує 30 одиниць даних, наданих з інших місць, все ще може мати щось із запахом коду. Рефакторинг об’єкта введення параметрів, мабуть, слід розглядати як крок у процесі ширшого процесу рефакторингу, а не як кінець цієї процедури.

Одне із занепокоєнь, яке воно насправді не стосується, - це питання Feature Envy. Чи той факт, що клас, що передається об'єкту Parameter, настільки зацікавлений у даних іншого класу, не вказує на те, що, можливо, методи, які оперують цими даними, слід перенести туди, де вони знаходяться? Насправді краще визначити кластери методів та даних, що належать разом, і згрупувати їх у класи, збільшуючи тим самим інкапсуляцію та роблячи ваш код більш гнучким.

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


10

Це відмінна ідея і дуже поширене рішення проблеми. Методи з більш ніж 2 або 3 параметрами стають експоненціально складнішими та важчими для розуміння.

Інкапсуляція всього цього в один клас робить набагато чіткіший код. Оскільки ваші властивості мають імена, ви можете написати код самодокументування таким чином:

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

Звичайно, коли у вас багато параметрів, альтернатива, заснована на позиційній ідентифікації, просто жахлива.

Ще однією перевагою є те, що додавання додаткових параметрів до інтерфейсного контракту може здійснюватися без примусових змін на всіх сайтах викликів. Однак це не завжди так тривіально, як здається. Якщо різні сайти викликів вимагають різних значень для нового параметра, то їх важче знайти, ніж за допомогою підходу, заснованого на параметрах. У підході на основі параметрів додавання нового параметра змушує зміни на кожному сайті виклику подавати новий параметр, і ви можете дозволити компілятору виконати роботу з їх пошуку всіх.



5

30 параметрів - це безлад. Я думаю, що набагато красивіше мати клас із властивостями. Ви навіть можете створити кілька "класів параметрів" для груп параметрів, які входять в одну категорію.


3

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

Але те, що ви намагаєтеся зробити, є дуже поширеним явищем і чудовою ідеєю!


Я насправді думав про використання структури, але мені було цікаво, чи будуть якісь недоліки, якщо я використовую структуру.
Xaisoft,

Чому Девід? Яке відношення має до цього кількість полів? Просто цікаво.
Tad Donaghe

2
Як питання продуктивності. Хоча семантично тут має більше сенсу використовувати структуру, оскільки має значення лише значення кожного поля, а ідентичність посилання не має значення, 30 або близько того поля більше копіювати (скажімо, це переважно посилальні типи; 120 байт на 32-бітах та 240 байт на 64), ніж із класом (4 або 8 байт). Однак природа конструкцій, що копіюється за значенням, означає, що після копіювання доступ буде швидшим, ніж із посилальним типом, тому поріг, коли struct є менш ефективним, вищий, ніж розмір 1-покажчика, який передбачений вище. При> 4 розмірах покажчика час розглянути його.
Джон Ханна,

Приголомшливо Дякую за пояснення! :)
Tad Donaghe

3

Цілком розумно використовувати клас Plain Old Data незалежно від того, чи рефакторингуєте ви чи ні. Мені цікаво, чому ви думали, що цього може не бути.


Я точно не пам’ятаю, але я думаю, коли я десь тут згадав, що я створюю клас із властивостями, у той час на нього дивилися зневажливо. Що стосується Вашого другого пункту, чи хочете ви сказати, що в метод повинні передаватися лише ті параметри, які не змінюються, іншими словами, якщо метод змінює параметр, його не слід передавати як параметр.
Xaisoft

Класи, що мають лише властивості, можуть спричинити неприємний запах (див. Відповідь Стіва Роуботема), що вказує на те, що натомість має бути «повним» класом. Хороший знак, якщо ви в кінцевому підсумку використовуєте подібні класи більше одного разу. Однак це не завжди так, і при переході між класом 30 полів і методом 30 параметрів є гарний заклик схилятися до першого. Крім того, це може бути початком побудови цього "повного" класу.
Джон Ханна,

... Я видаляю свій коментар щодо незмінності, оскільки я більше думав, що клас цього є лише загальнодоступним методом (де об'єкт "запит" описує намір абонента). Тут зміна "запиту" може призвести до плутанини. В одноразовому випадку зручніше бути мінливим і будувати по ходу. Незмінність означала б просто заміну 30-аргументного виклику на 30-аргументний конструктор, за яким слідує виклик, тому виграшу не буде.
Джон Ханна,

3

Можливо, додаткові та названі параметри C # 4.0 є хорошою альтернативою цьому?

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

Ось як це робить діалогове вікно збереження в Paint.NET, тому це дуже реальний приклад із життя.


3

Як зазначалося раніше: це правильний крок, але враховуйте також наступне:

  • ваш метод може бути занадто складним (вам слід подумати про розподіл його на більше методів або навіть перетворити його в окремий клас)
  • якщо ви створюєте клас для параметрів, зробіть його незмінним
  • якщо багато параметрів можуть бути нульовими або мати якесь значення за замовчуванням, ви можете використовувати шаблон конструктора для свого класу.

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

Таке використання допоможе вам розкрити можливості додавання значущої поведінки до цих об’єктів параметрів. Ви виявите, що вони швидше струшують свій початковий запах класу даних для вашого комфорту. : -)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.