Подтвердить что ты не робот

Вопрос проектирования: передайте поля, которые вы используете или передаете объект?

Я часто вижу две противоречивые стратегии для интерфейсов методов, которые кратко суммируются следующим образом:

// Form 1: Pass in an object.
double calculateTaxesOwed(TaxForm f) { ... }

// Form 2: Pass in the fields you'll use.
double calculateTaxesOwed(double taxRate, double income) { ... }

// use of form 1:
TaxForm f = ...
double payment = calculateTaxesOwed(f);

// use of form 2:
TaxForm f = ...
double payment = calculateTaxesOwed(f.getTaxRate(), f.getIncome());

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

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

Есть ли убедительный общий случай для любой формы? Есть ли ясные примеры того, когда вы должны использовать вторую форму в течение первого? Есть ли СОЛИДНЫЕ или другие принципы ООП, которые я могу указать, чтобы оправдать мое решение использовать одну форму над другой? Изменится ли какой-либо из вышеперечисленных ответов, если вы используете динамический язык?

4b9b3361

Ответ 1

По правде говоря, это зависит от рассматриваемого метода.

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

Если метод полагается на объект, то достаточно справедливо передать объект.

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

Ответ 2

Это зависит от намерения вашего метода.

Если метод предназначен для работы специально с этим объектом и только этот объект, передайте объект. Это делает приятную инкапсуляцию.

Но если метод более общий, вы, вероятно, захотите передать параметры индивидуально. Таким образом, метод, скорее всего, будет повторно использоваться, когда информация будет из другого источника (т.е. Различные типы объектов или другие производные данные).

Ответ 3

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

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

UPDATE

Если налоговая форма не является формой пользовательского интерфейса, все немного меняется. В этом случае я предлагаю разоблачить значение, используя метод экземпляра GetOwedTaxes() или свойство экземпляра OwedTaxes класса TaxForm, но я бы не использовал статический метод. Если вычисление может быть повторно использовано в другом месте, все равно можно создать статический вспомогательный метод, который использует значения, а не форму, и вызовет этот вспомогательный метод из метода или свойства экземпляра.

Ответ 4

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

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

Судите каждую ситуацию по существу и выбирайте наименее болезненный.

Ответ 5

Передача только аргументов может быть проще unit test, так как вам не нужно макетировать целые объекты, заполненные данными, просто для проверки функциональности, которая по сути является просто статическим вычислением. Если используется только два поля, из объекта много, я бы склонялся к тому, чтобы просто передавать эти поля, при прочих равных условиях.

Тем не менее, , когда вы закончите с шестью, семью или более полями, пришло время рассмотреть передачу всего объекта или подмножества полей класса "полезная нагрузка" (или struct/словарь, в зависимости от языкового стиля). Длинные сигнатуры методов обычно сбивают с толку.

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

Ответ 6

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

double payment = f.calculateTaxesOwed;

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

Ответ 7

Одним преимуществом первой формы является

  • Абстракция - программирование для интерфейса, а не для реализации. Это упрощает обслуживание вашего кода в долгосрочной перспективе, потому что вы можете изменить реализацию TaxForm, не затрагивая код клиента, если интерфейс TaxForm не изменится.

Ответ 8

Это то же самое, что и "Ввести объект параметров" из книги Мартина Фаулера по рефакторингу. Фаулер предлагает вам выполнить этот рефакторинг, если есть группа параметров, которые, как правило, передаются вместе.

Ответ 10

Разделение пользовательского интерфейса и данных для управления

В вашем случае вам не хватает промежуточного класса, скажем, TaxInfo, представляющего объект, подлежащий налогообложению. Причина в том, что пользовательский интерфейс (форма) и бизнес-логика (как рассчитывается ставка налога) находятся на двух разных "дорожках изменения", один из которых изменяется с помощью технологий представления ( "Интернет", "Веб 2.0", "WPF",...), другие изменения с legalese. Определите четкий интерфейс между ними.


Общее обсуждение, используя пример:

Рассмотрим функцию создания растрового изображения для визитной карточки. Является ли цель функции

(1)//Форматирует название визитной карточки с именем и фамилией

ИЛИ

(2)//Форматирует заголовок карточки busines из записи Person

Первый вариант более общий, с более слабым соединением, которое обычно предпочтительнее. Однако во многих случаях менее надежны против запросов на изменение - например, рассмотрите "случай 2017: добавьте лиц с первоначальной визитной карточкой".

Изменение реализации (добавление person.Initial) обычно проще и быстрее, чем изменение интерфейса.

В конечном итоге выбор заключается в том, какие типы изменений вы ожидаете: более вероятно, что требуется больше информации из записи Person или, что более вероятно, что вы хотите создавать названия визитных карточек для других структур данных, чем Person?

Если это "undefined", anfd вы не можете использовать opf для цели (1) или (2), я предпочитаю использовать (2) для синтаксической чистоты.

Ответ 11

Если бы меня заставили выбрать один из двух, я бы всегда пошел со вторым - что, если вы обнаружите, что вам (по какой-то причине) нужно окупить налоги, но у вас нет экземпляра TaxForm?

Это довольно тривиальный пример, однако я видел случаи, когда метод, выполняющий относительно простую задачу, имел сложные входы, которые трудно было создать, что затрудняло использование метода, чем следовало бы. (Автор просто не считал, что другие люди могут захотеть использовать этот метод!)

Лично для того, чтобы сделать код более читаемым, я бы, вероятно, имел бы оба:

double calculateTaxesOwed(TaxForm f) 
{
    return calculateTaxesOwed(f.getTaxRate(), f.getIncome());
}

double calculateTaxesOwed(double taxRate, double income) { ... }

Мое эмпирическое правило - везде, где это возможно, метод, который принимает именно тот вход, который ему нужен - его очень легко написать методы обёртки.

Ответ 12

Лично я пойду С# 2, потому что это намного более ясно из того, что нужно, чтобы этот метод нуждался. Передача TaxForm (если это то, что я думаю, это как форма Windows), является вонючей и заставляет меня немного съеживаться ( > _ <).

Я бы использовал первый вариант, только если вы передаете DTO, специфичный для вычисления, например объект IncomeTaxCalculationInfo, который будет содержать TaxRate и Income и все, что необходимо для вычисления конечного результата в методе, но никогда не будет похоже на Windows/веб-форма.