Передача одного объекта по сравнению с передачей нескольких параметров

Предположим, что у меня есть

Class A {
    Foo getFoo();
    Bar getBar();
    Baz getBaz();
}

И мне нужно определить функцию doStuff, которая использует Foo, Bar, Baz объекта один и делает некоторые вещи

Я боюсь между тем, какой метод реализации doStuff лучше (предположим, было бы нежелательно размещать doStuff внутри класса A)

Метод A

void doStuff(Foo foo, Bar bar, Baz baz)
{ 
    //some operation
}

или

Метод B

void doStuff(A a)
{
    Foo foo = a.getFoo();
    Bar bar = a.getBar();
    Baz baz = a.getBaz();
    //some operation
}

К моим ограниченным знаниям, (+ плюсы, минусы)

Метод A

+ Ясно, какие параметры doStuff() работают на

-Почувствуйте длинные списки параметров и более подвержены ошибкам пользователя

Метод B

+ Простой, простой в использовании метод

+ Кажется более растяжимым (?)

-Создает ненужную зависимость от класса A


Может ли кто-нибудь поделиться дополнительной информацией о плюсах и минусах этих двух методов?

Ответ 1

Метод A (голые параметры) всегда имеет преимущества, которые

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

Метод B (Объект параметров) имеет преимущества, когда

  • параметры имеют значение домена как группу, поэтому объекту Parameter может быть присвоено имя, которое объясняет это значение, что позволяет читателю читать и понимать каждого члена группы и как они соотносятся
  • список параметров используется более чем одним способом, поэтому использование объекта параметра в каждом случае уменьшает дублирование.
  • значения в списке параметров передаются среди нескольких методов в виде группы, что проще, когда они могут передаваться как один объект параметра
  • некоторые комбинации значений недействительны; объект Parameter может предотвратить эти комбинации
  • некоторые значения являются необязательными, которые могут быть предоставлены объектом Parameter вместо (по вашему языку) значений параметров по умолчанию или перегруженных методов.
  • существует более одного параметра одного и того же типа, что делает ошибки переопределения значений более вероятными (хотя объект Параметр не лучше в этом случае, если он имеет конструктор с тем же списком параметров, что и метод)

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

Итак, объект параметра

  • почти никогда не стоит для одного параметра, иногда стоит использовать двухпараметрический метод (например, точка обычно лучше, чем x, y), а иногда и нет, и все более полезно с тремя и более параметрами
  • становится все более полезным, когда другие методы используют один и тот же список параметров

Ответ 2

Parameter Object обеспечивает хороший подход для инкапсуляции параметров related, чтобы уменьшить общее количество параметров для любого метода или конструктора. Нужно быть очень осторожным, чтобы убедиться, что объекты параметров действительно содержат действительно связанные параметры.

На самом деле существует несколько способов решения этой проблемы в зависимости от того, с каким parameter types вы имеете дело. Если вы имеете дело с параметрами, которые являются общими типами, такими как более одного String или Int, и существует вероятность того, что клиент фактически передаст неверную последовательность аргументов, часто имеет смысл создать custom types, т.е., создать enum с возможными значениями. Это может обеспечить хорошую проверку времени компиляции для ваших аргументов. Еще одно хорошее применение - вы можете использовать их для return сложных значений из функций. Смотрите здесь.

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

В основном я стараюсь следовать рекомендации Боба Мартина максимум из трех параметров. Ну, на самом деле он говорит, что это должно быть в основном не более одного! Любое увеличение должно иметь обоснованные причины. Обратитесь к этой превосходной книге: Чистый код

Ответ 3

И ответы Дэвида, и Сома имеют большую информацию для рассмотрения. Я добавлю следующее:

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

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

Например, методы A, B, C и D принимают Foo, Bar и Baz, поэтому неплохо объединить эти аргументы в DTO. Тогда методы A и B должны взять Quux - вы добавляете Quux в DTO, заставляя C и D брать неиспользуемую зависимость? Когда вы тестируете C и D, какое значение вы передаете для Quux? Когда новый разработчик использует методы C и D, вызывает ли путаница присутствие Quux? При сравнении методов A и C ясно, как Quux должен или не должен быть определен?

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

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

Если значения не всегда совпадают, вы рискуете создать путаницу, увеличить нагрузку на тест и лишнюю работу разработчика. Если значения всегда идут вместе, DTO может обеспечить ясность, уменьшить дублирование, упростить согласованность и т.д.

Ответ 4

Рассмотрим клиента, который имеет адрес и CurrentInvoice. Что более правильно -

SendInvoiceToAddress(Invoice invoice, Address adress);

или

SendInvoiceToAddress(Customer customer);

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

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

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