Вместо вступления
Тема достаточно капитанская, поэтому изначально идея была здравая и в немалой степени благородная - написать небольшой, но жизненный рассказ, иллюстрирующий преимущества Fluent API нескучным языком, а в конце уже более сухо изложить основные моменты. Чтобы с одной стороны можно было быстро все прочитать и не сожалеть о бесполезно потраченном времени, а с другой люди с дифицитом времени могли бы просто промотать к сути и быстро убедиться что ничего нового не узнали.
Но, как это часто бывает, что-то пошло не так и рассказ получился неприлично большим, а основные моменты сами потянули на статью. Так что рассказ уехал под спойлер - может кто захочет почитать, а статья получилась приемлемой по размеру.
История становления Fluent мышления в отдельно взятой голове
Продолжаем разговор о Fluent API. И теперь, после того как мы из предыдущей статьи (или/и личного опыта) узнали о том что это чудо из себя представляет давайте разберемся зачем оно нужно.
Как мы знаем по сериалам про 90-е ни одна разборка не проходит без людей, поэтому знакомьтесь - члены ОПГ сотрудники фирмы “ИП Иванов Петр Сидорович” (все имена вымышлены, любые совпадения случайны, примеры кода умышленно упрощены):
Генеральный директор Иванов Петр Сидорович;
Его секретарша Светочка;
Глав бух Светлана Юрьевна (не дай бог назвать Светочкой);
Завхоз Потеряйко;
Главный специалист Константин Рукоплечев;
Водитель Петрович;
Охранник ”Кастет”;
И, наконец, наш герой - человек-программист Валера;
Валера уже который год трудится над корпоративным порталом и как раз дошел до раздела контакты, в котором решил просто отобразить список сотрудников. Да, решил. Да, сам. Системного аналитика же нет. А постановка задачи на портал от генерального так и звучала:
- Валера, нам нужен корпоративный портал!
Итак, поправив очки и мобилизовав левое полушарие мозга, Валера приступил к аналитике раздела контакты. Через три минуты аналитика была готова, так как другой информации о сотрудниках, кроме представленной выше, у Валеры не было.
Итого, получается что у нас есть сущность - сотрудник (aka employee) с четырьмя атрибутами:
Имя (Name)
Отчество (Patronymic)
Фамилия (Surname)
Должность (Position)
Немного портил картину охранник, так как не очень понятно к чему отнести “Кастет” - к имени или фамилии. Валера даже грешным делом думал ввести новый атрибут - кличка, но в конце концов волевым усилием решил что “Кастет” это имя.
Закатав засаленные рукава свитера (ну или на современном диалекте - заказав смузи) Валера реализует метод добавления сотрудников с такой сигнатурой:
long registerEmployee(String name,
String patronymic,
String surname,
String position);
Все что знаем про сотрудника подаем на вход, а что не знаем (идентификатор) получаем на выходе.
Перфекционист внутри Валеры поворчал для порядка мол “максимальное количество параметров для метода 3-4” но так как пока параметров не больше максимального максимума умолк до поры.
Пока проект собирался, тестировался и деплоился в облако, Валера прочитал про перегрузку методов и с энтузиазмом неофита реализовывает отдельные методы для каждого из вариантов регистрации:
//– по имени для Светочки
long registerEmployee(String name,
String position);
//– по отчеству для Петровича
long registerEmployee(String patronymic,
String position);
//– по фамилии для Потеряйко
long registerEmployee(String surname,
String position);
//– по имени и отчеству для Светланы Юрьевны
long registerEmployee(String name,
String patronymic,
String position);
//– по имени и фамилии для Константина Рукоплечева
long registerEmployee(String name,
String surname,
String position);
Откинувшись на спинку протертого кресла Валера любуется на написанный код - ему нравится. Вот только компилятору почему-то не нравится. А в споре с компилятором Валера обречен на поражение. Пришлось отступить, предприняв неожиданный для компилятора маневр, и немного отрефакторить так радующий глаз код:
//– по имени для Светочки
long registerEmployeeWithName(String name,
String position);
//– по отчеству для Петровича
long registerEmployeeWithPatronymic(String patronymic,
String position);
//– по фамилии для Потеряйко
long registerEmployeeWithSurname(String surname,
String position);
//– по имени и отчеству для Светланы Юрьевны
long registerEmployeeWithNameAndPatronimyc(String name,
String patronymic,
String position);
//– по имени и фамилии для Константина Рукоплечева
long registerEmployeeWithNameAndSurname(String name,
String surname,
String position);
Компилятор успокоился, зато восстал из забвения внутренний перфекционист, мол была обещана перегрузка, а это совсем не она. И не дай бог в имени метода параметры указать не в том порядке в котором они следуют в сигнатуре - проблем не оберешься.
Тучи техдолга сгустились над Валерой, внутренний перфекционист на этот раз отступать не собирался, а второго поражения за день наш герой допустить никак не мог. И тогда как молния сверкнула мысль:
- Джун ли я копипастящий, или рефакторить умею?
Заправив свитер поглубже в штаны (отхлебнув смузи) за пару часов и десять перекуров Валера являет миру следующее:
@Builder
record Employee(long id,
String name,
String patronymic,
String surname,
String position) {
}
Employee register(Employee employee);
Но перфекционист не унимается: Пять! Пять! Пя-а-а-а-ть! Неоднозначно намекая на то что количество параметров в конструкторе не совместимо с best-of-the-best practices. И тогда Валера с невозмутимым видом указывает на аннотацию @Builder и демонстрирует невообразимую легкость построения различных конфигураций сотрудников с применением Fluent билдера, дарованного вышеозначенной аннотацией:
var svetochka = Employee.builder()
.name("Светочка”)
.position("Секретарь шефа")
.build();
И , например:
var shchef = Employee.builder()
.name("Петр")
.patronymic("Сидорович")
.surname("Иванов")
.position("Шеф")
.build();
Это была победа! Компилятор с перфекционистом довольны, хотя перфекционист глубоко в душе подозревает что его надули, но предъявить формально нечего. Валера сделал первый вывод в отношении Fluent API:
Код написанный с применением данного подхода зачастую читается легче и более однозначно
Но время идет, количество сотрудников растет, и наконец настал тот светлый день, когда в компании появился HR Леночка. Однако, этот день чуть не стал последним для новоиспеченного специалиста по кадрам. Молодое сердце не без труда, но выдержало удар, нанесенный списком сотрудников. После горсти успокоительных, экстренной онлайн консультации с психоаналитиком и получасовой медитации Леночка со словами:
- Валера, ну какой “Кастет?”
и тиком правого глаза вошла в каморку программиста. Кроме нескольких новых нецензурных оборотов из того монолога Валера вынес что у Петровича отчество Николаевич, а Петрович это фамилия, у Светочки нет отчества, но зато есть матчество - Екатериновна, а “Кастет” и вовсе Илья Святогорович Подопригора.
Однако это знание принесло с собой тяжкое бремя осознания того что теперь придется добавлять проверку наличия имени и фамилии для всех вновь добавляемых в базу сотрудников + делать метод для модификации существующих.
С одной стороны что может быть проще:
@Builder
record Employee(long id,
@NonNull
String name,
String patronymic,
@NonNull
String surname,
@NonNull
String position) {
}
Но с другой возникают вопросы:
-
А как обновить только отчество? Ведь после добавления проверок невозможно создать
Employeeтолько с отчеством. Это что ж создавать полноценный объект? Но тогда будет обновлена и фамилия, а по ней индекс в БД, а индекс перестроится - это же не эффективно. Тут надо отметить 2 момента:Эффективность это второе имя Валеры;
Он пока не дочитал статью про индексы и не знает что даже обновление не индексированного поля может привести к модификации индекса;
Как интерпретировать
nullв полеmiddleName- как то что мы хотим его обнулить или что мы просто хотим оставить старое значение?Да и вообще, где брать актуальный экземпляр
Employee? Не делать же отдельный метод для того чтобы получать его по первичному ключу из БД. А получать полный список из БД неэффективно, ведь информацию о шефе, например, менять не надо (не забываем что эффективность это второе имя Валеры)
Второй подход к снаряду модификации информации о сотрудниках принес целый список методов:
boolean updateName(long id, String name);
boolean updatePatronimyc(long id, String patronymic);
boolean updateSurname(long id, String surname);
C одной стороны получше, все понятно. Можно прицельно поменять и обнулить атрибут. Но с другой чтобы поменять учетную запись “Кастету” нужно будет делать целых 3 апдейта в БД! И тут тоже 2 момента:
Он дочитал статью про индексы и теперь знает что обновление Не индексированного поля также может привести к обновлению индекса, а это потенциально 3! обновления индекса на одного “Кастета”;
А второе имя Валеры никуда не делось;
Потратив на рефлексию еще какое-то время и несколько литров кофе Валера решил что пока хватит валидации полей в Employee. А для модификации данных зарегистрированных сотрудников он сделает SQL скрипт и прольет его на продакшен базе со следующим релизом.
Принятое решение приятной волной тепла и уверенности в своих силах покатилась от макушки в направлении любимых кроссовок, заставив Валеру в блаженстве откинуться на спинку кресла и устремить взгляд поверх монитора где висел плакат с девизом всех (ну почти всех) программистов:
“Компилируется - значит работает!”
Немного не дойдя до лодыжек волна сменила температуру с теплой на холодную, и потекла обратно к голове мобилизуя всего Валеру для очередного подвига.
Причиной, прервавшей заслуженное блаженство было то что код типа:
var svetochka = Employee.builder()
.name("Светочка”)
.position("Секретарь шефа")
.build();
успешно скомпилируется, но упадет в рантайме, что не соответствует девизу.
Мобилизованный Валера заправляет бороду за пояс (берет латте в автомате, сколько можно пить смузи!) и реализует билдер, который гарантирует что все обязательные поля будут заполнены при конструировании экземпляра сотрудника. (Как это сделать в реальности мы рассмотрим в последующих статьях).
Теперь можно написать либо так:
var emplopyee = Employee.builder()
.name("Имя")
.patronymic("Отчество")
.surname("Фамилия")
.position("Должность")
.build();
Либо так:
var emplopyee = Employee.builder()
.name("Имя")
.surname("Фамилия")
.position("Должность")
.build();
И в момент компиляции будет проверено что обязательные части билдера вызываются, что уменьшает вероятность ошибки при создании сотрудника!
Очередная волна тепла и уверенности побежала в сторону кроссовок и на этот раз ничто ей не мешало. А Валера сделал для себя второй вывод:
Fluent подход к проектированию интерфейсов позволяет контролировать вызов обязательных шагов процесса в момент компиляции
Ровно через минуту после обновления версии списка сотрудников на корпоративном портале в холостой рабочий день Валеры вошла еще одна женщина - Светочка. Кроме очередной порции новых нецензурных выражений Валера уловил расстройство Светочки по поводу значения "Екатериновна" в графе отчество напротив ее фамилии. И пожелание либо оставить значение пустым либо добавить отдельный атрибут - матчество.
Для решения этой проблемы Валере не пришлось ничего ни закатывать ни заправлять. Прямо на глазах у Изумленной Светочки он добавляет новый атрибут в объект сотрудника:
record Employee(long id,
String name,
String patronymic,
String matronymic,
String surname,
String position) {
}
И слегка модифицирует билдер так, что теперь можно писать еще и так:
var emplopyee = Employee.builder()
.name("Имя")
.matronymic("Матчество")
.surname("Фамилия")
.position("Должность")
.build();
При этом код, написанный ранее работает точно также и его менять не нужно! А Валера делает пару выводов:
Лучшим источником обучающей выборки нецензурных выражений являются расстроенные женщины
Грамотно спроектированный Fluent API легко поддается расширению и помогает обеспечивать обратную совместимость для кода, использующего такой API
И, казалось бы успех и всеобщее ликование, но HR Леночка, не знающая отдыха в своем стремлении к причинению неизбежного и непоправимого блага предлагает:
- А давайте будем поздравлять сотрудников с днями рождения?! А еще с восьмым марта женскую половину и с 23 февраля мужскую? Ну конечно только тех кто хочет чтобы его поздравляли...
Ежу понятно, не говоря о Валере, что для этого необходимо к имеющимся данным добавить дату рождения и пол.
А вот что ежу не понятно, но Валере-то совершенно очевидно, что теперь то уж точно нужны методы получения и обновления данных сотрудника, так как редактировать их будут сами сотрудники на портале. Ну а за одно и метод удаления предусмотреть, мало ли чего…
Благо к этому времени скилл проектирования Fluent API зарожденный аннотацией @Builderи эволюционировавший в процессе рефакторинга билдера уже вышел на качественно новый уровень. Так что Валера заплел бороду в косы на манер скандинавских викингов перед битвой и впал в состояние берсерка вспомнив запас нецензурных выражений, позаимствованных от коллег.
Когда кровавая пелена очередного рефакторинга спала с глаз, в редакторе красовался абсолютно новый код репозитория.
Сотрудник теперь был представлен интерфейсом:
enum Sex {
MALE,
FEMALE
}
interface Employee {
long id();
String name();
String patronymic();
String matronymic();
String surname();
String position();
LocalDate birthday();
Sex sex();
}
А вся работа со списком сотрудников выглядела следующим образом:
var repository = new EmployeesRepository();
Employee employee = repository.employee().name("Имя")
.patronymic("Отчество")
.surname("Фамилия")
.position("Должность")
.sex(Sex.MALE)
.register();
Employee employee = repository.employee(12L).get();
boolean successs = repository.employee(12L)
.birthday(LocalDate.of(2001, 2, 4))
.update();
boolean success2 = repository.employee(12L).delete();
Валера перечитал написанный код несколько раз не отрываясь, и каждый раз сумма его зарплатных ожиданий увеличивалась, в арифметической, нет, геометрической прогрессии, а в голове параллельно генерировались 2 набора слов - те, которые сказать генеральному и те, которые нужно написать в резюме если генеральный откажется материализовать виртуальный оклад из головы Валеры на его же банковскую карту.
Очередной вывод не заставил себя долго ждать:
Применение Fluent подхода способствует избавлению от методов с большим количеством параметров и от лишних DTO
В плане коммуникативных навыков Валера был далек от идеала (да ладно до идеала, ему и до нормы было как до точки, в которой пересекаются параллельные прямые) а поэтому предстоящий визит к генеральному пугал его пуще самой страшной рекламации на проде, случившейся в 00:00 первого января. Тут никакие магические манипуляции со свитером или бородой не помогут. Но обычная человеческая алчность, прикинувшись чувством справедливости, буквально вытолкнула его из кабинета, где в красные от недосыпа из-за ночных партий в доту глаза Валеры безжалостно ударил свет потолочных светильников, показавшийся ему после уютного полумрака рабочей пещеры поистине нестерпимым. Однако ничто не могло уже остановить нашего героя на пути установления справедливости в отношении отдельно взятого сотрудника интеллектуального труда.
Вопреки ожиданиям Петр Сидорович не удивился увидев Валеру, но даже обрадовался. Не дав тому даже начать гениальную в своей убедительности речь, хозяин кабинета поведал, что теперь они не просто маленькое ИП, а часть большого и хорошо отлаженного механизма, так как их купил самый крупный игрок на отечественном рынке. И теперь им предстоит много работы по интеграции инфраструктуры ИП в экосистему родительской ассоциации.
Не выдержав напора новой информации, усиленной харизмой Иванова, чувство справедливости, до того пылающее золотым заревом, потухло и обернулось изначальной алчностью, а заготовленная речь Валеры деградировала до “Петр Сидорович, мне бы немного, может, это зарплату бы, а?”.
У Петра Сидоровича же в голове на полной громкости играли медные трубы, поэтому слова Валеры он не услышал, но, его запрос он понял быстро (еще бы, абы кого не берут в ген директора) и отреагировал стандартным в такой ситуации образом - выдал тираду на пол часа, кратко сводящуюся к следующему “Валера, иди работай, может быть по результатам года. Следующего, ну или на крайний случай через два-три”.
Возвращался в свой кабинет Валера уже ведущим разработчиком, нет, тимлидом в самой большой ассоциации в стране! Ну во всяком случае он себя так ощущал и само по себе это было настолько приятно, что алчность, махнув рукой, залегла в спячку до поры до времени.
Это состояние полета помогло Валере начать общение с коллегами из родительской организации, которые впрочем оказались не очень общительными, сказали что интересует их только список сотрудников, и то, только для интеграции с ним. Чтобы можно было из их централизованной системы учета персонала выполнять операции и с сотрудниками Валериного ИП. Интеграцию они взялись сделать сами, но для этого запросили необходимую документацию:
Требования (фукнциональные и нефункциональные)
Описание архитектуры
Описание АПИ
Документация для разработчиков
Методика тестирования
Отчет о тестировании на уязвимости
Траблшутинг гайд
…
В общем только самое необходимое.
Передав для начала исходники, Валера пообещал что документация будет в ближайшем будущем, и, отключившись от конференции, начал прогресс мутации в гибрида системного аналитика и технического писателя с изучения статей на тему документирования программного обеспечения со всех возможных сторон.
На следующий день, заметив непрочитанное сообщение в чатике с коллегами по глобализации списка сотрудников, Валеру прошиб было холодный пот, так как от аналитика у него пока отпочковалось только понимание различий в видах требований, а от технического писателя умение расставлять заголовки в документах. Но после прочтения самого сообщения в Валере с новой силой вспыхнуло ощущение причастности к великому, так как коллеги отписали, что уже провели интеграцию с репозиторием сотрудников ИП “Иванов Петр Сидорович”, просто потыкавшись в присланный код. Документацию конечно никто не отменял, но теперь срочность в ее передаче сильно снизилась, да и уважение к Валере со стороны коллег поднялось выше плинтуса и даже чуть привстало с колен.
Последние выводы Валеры в отношении Fluent API в бытность его программистом ИП “Иванов Петр Сидорович” были такими:
Грамотно спроектированный Fluent API снижает порог входа при знакомстве с новым кодом реализующим этот API
Всем приятно почитать код, который написан не просто чтобы работал, но с заботой о тех, кто будет его использовать
Да, да. Вы все правильно поняли. Прошло совсем немного времени и Валеру перевели на должность ведущего разработчика в родительскую компанию, в которой он продолжил свою карьеру в роли техлида, архитектора, а потом и Валерия Николаевича - главного архитектора всей ассоциации. Но это уже совсем другая история…
И так...
Положа руку на сердце, необходимо признать что суровой необходимости в применении Fluent подхода нет. Более того, в некоторых случаях он даже противопоказан (в каких конкретно мы посмотрим в будущих статьях).
Однако в ряде случаев он существенно повышает качество кода за счет:
1. Простоты понимания и однозначности восприятия:
Рассмотрим упрощенный пример перевода денежной суммы. Сначала классический подход:
bank.transfer(1L, 2L, 1200, 1.2);
А теперь тот же функционал но с Fluent подходом:
bank.fromCustomerId(1L)
.toCustomerId(2L)
.amount(1200)
.withFee(1.2)
.transfer();
Комментарии кажутся излишними.
2. Возможности контроля в момент компиляции вызова обязательных шагов и их последовательности:
Вообразим себе класс, который строит дома. Дом у нас достаточно примитивный и состоит из фундамента, какого-то количества этажей и крыши.
Фундамент может быть лента, плита (
STRIP/SLAB)Этаж может быть жилой или коммерческий (
RESIDENTIAL/COMMERCIAL)Крыша плоская или двускатная (
FLAT/GABLE)
Тогда в классическом подходе будет примерно так:
Builder builder = new Builder();
builder.addFoundation(STRIP);
builder.addFloor(COMMERCIAL);
builder.addFloor(RESIDENTIAL);
builder.addRoof(FLAT);
Building result = builder.build();
В то время как с применением Fluent подхода:
Building building = Builder.withFoundation(STRIP)
.withFloor(COMMERCIAL)
.withFloor(RESIDENT)
.withRoof(FLAT)
.build();
И если в первом примере добавить этаж после фундамента, фундамент после крыши, забыть какую-то часть или добавить несколько фундаментов/крыш разработчику помешает только исключение времени выполнения, то во втором он просто не сможет написать некорректный код. У него всегда будет один фундамент, не менее одного этажа и только одна крыша. И проверит это компилятор.
В то же время если у нас появилась необходимость добавлять чердак в обязательном порядке (а он может быть теплый и холодный WARM/COLD), то мы добавляем обязательный шаг в наш Fluent API и получаем:
Building building = Builder.withFoundation(STRIP)
.withFloor(COMMERCIAL)
.withFloor(RESIDENT)
.withAttic(WARM)
.withRoof(FLAT)
.build();
При этом старый код, в котором чердаки были не предусмотрены станет невалидным и не скомпилируется.
При классическом подходе мы тоже добавим шаг:
builder.addAttic(WARM);
Но вот куски кода, которые не добавляли чердаки к домам нужно будет либо искать руками либо отлавливать через исключения в рантайме.
3. Легкости расширения и автоматической обратной совместимости:
Продолжим банковскую тематику из первого пункта. В новом модуле у нас появилась необходимость добавлять строковую информацию к переводу. В то же время для старых это не требовалось.
По классике мы расширяем сигнатуру метода:
bank.transfer(1L, 2L, 1200, 1.2, "Details");
и делаем перегрузку для сохранения совместимости:
void transfer(long source, long target, int amount, float fee){
transfer(source, target, amount, fee, null);
}
И в целом ОК, но те кто видел многолетний результат таких итераций намек поймут…
В тоже время Fluent API мы добавим новый необязательный шаг, позволив писать:
bank.fromCustomerId(1L)
.toCustomerId(2L)
.amount(1200)
.withFee(1.2)
.withDetails("Details")
.transfer();
При этом старый код будет успешно компилироваться и работать.
Если обобщить с предыдущим пунктом то мы получаем гибкость, обратную совместимость и контроль “в одном флаконе”.
4. Избавлению от лишних DTO и методов с большим количеством параметров:
Нередко возникает необходимость передать в какой-либо метод большое количество параметров (> 4) и тогда перед разработчиком стоит нелегкий выбор - смириться с тем что количество параметров не по фэншую или передавать DTO. При этом этот DTO скорее всего никому больше и не нужен. А у DTO конечно будет билдер, который в коде будет привлекать внимания существенно больше чем заслуживает по своей значимости.
Не уходя далеко от так полюбившихся банков, можем набросать пример:
TransferDto dto = TransferDto.builder()
.withSourceCustomerId(1L)
.withTargetCustomerId(2L)
.withAmount(1200)
.withFee(1.2)
.withDetails("Details")
.build();
bank.transfer(dto); <-- полезный метод
То есть тут нам рассказывается история о том как самоотверженно мы строим DTO только для того чтобы передать его в метод перевода. Хотя этот пример уже намного лучше чем просто вызов одного метода с неочевидными параметрами.
В случае с Fluent API нам код рассказывает собственно про сам перевод, что кажется более логичным. Напомню:
bank.fromCustomerId(1L)
.toCustomerId(2L)
.amount(1200)
.withFee(1.2)
.transfer();
5. Снижение порога входа при знакомстве с кодом:
Если вернуться к классическому примеру из первого пункта:
bank.transfer(1L, 2L, 1200, 1.2);
Что можно сказать глядя только на этот код?
Что это метод перевода;
По имени переменной экземпляра класс видимо банковский перевод;
Что 1200 это сумма перевода, скорее всего;
Что 1.2 предположительно комиссия, но вот она сверху суммы или уже в сумме - не понятно. Судя по цифрам скорее всего в сумме, но это не точно;
Что 1L и 2L это идентификаторы источника и приемника.
Казалось бы не так чтобы очень мало, но вопросы при этом остались:
В каком порядке идут идентификаторы? Сначала приемник потом источник или наоборот?
К черту подробности! Это идентификаторы чего? Клиента? Счета? Карты?
Комиссия она все-таки включена или нет?
Не сказать что вопросы по мелочам, верно?
Что можно было бы сделать? Мы могли бы назвать метод как-нибудь так:
bank.transferFromSourceCustomerIdToTargetCustomerIdAmountWithFeeIncluded(1L, 2L, 1200, 1.2);
Судить, насколько улучшилась понятийная составляющая, оставлю читателю. Однако замечу что сама фраза, заключенная в имени методе похожа на Fluent вариант, только значения аргументов далеко от своих описателей. Ну и информацию о том включена комиссия или нет мы также можем добавить и в fluent метод:
bank.fromCustomerId(1L).toCustomerId(2L).amount(1200).withFee(1.2).included()
.transfer();
Вроде бы практически идентично. Но представим ситуацию что у нас есть варианты перевода:
Со счета на счет с включенной комиссией
Со счета на счет с дополнительной комиссией
От клиента клиенту с включенной комиссией
Со счета клиенту …
...
Нетрудно подсчитать общее количество вариантов
Тогда нам потребуется много методов с именами вида;
transferFromSourceCustomerIdToTargetCustomerIdAmountWithFeeIncluded
transferFromSourceCustomerIdToTargetCustomerIdAmountWithFeeExcluded
transferFromSourceAccountIdToTargetAccountIdAmountWithFeeExcluded
transferFromSourceAccountIdToTargetCustomerIdAmountWithFeeIncluded
...
Каждый раз выпадая в редакторе этот список “подсказок” IDE будет вызывать резь в глазах разработчика плавно перетекающую в головную боль и далее прямиком в техдолг.
При Fluent подходе мы будем видеть только те подсказки, которые доступны на конкретном шаге.
Сначала:
fromCustomerId //-- если источник задан идентификатором клиента
fromAccountId //-- если источник задан идентификатором счета
Затем:
toCustomerId //-- если приемник задан идентификатором клиента
toAccountId //-- если приемник задан идентификатором счета
Потом без вариантов:
amount
Следом:
withFee //-- если перевод идет с комиссией
transfer //-- если хотим создать перевод без комиссии
Если выбрали вариант с комиссией:
included //-- если комиссия включена в сумму перевода
excluded //-- если комиссия “накручивается” на сумму перевода
Ну и последним шагом в при любом исходе будет:
transfer //-- создание перевода
Именно он запустит весь маховик и осчастливит человека, которому осуществляется перевод, независимо от того указан его идентификатор или идентификатор одного из его счетов.
Счастье бы полным, если бы можно было скрывать стандартные методы от Object. Тогда в подсказках были бы только нужные методы. Но это нам в java недоступно, оставаясь прерогативой других ЯП.
Конечно все эти плюсы есть только у хорошо спроектированного и уместного Fluent API. А вот как его проектировать и когда в принципе применять мы и поговорим в следующих статьях.
Поздравляю! Мы успешно закончили с простыми и достаточно очевидными темами и переходим к интересностям.
Спасибо за уделенное время.
surly
По поводу улучшения читаемости:
При вызове с именованными параметрами мы добиваемся той же читаемости:
bank.transfer(fromCustomerId: 1L,toCustomerId: 2L,amount: 1200,fee: 1.2);Стоит ли усложнять систему билдерами, если язык позволяет пойти таким путём?
Woodroof
Или, если не позволяет,
bank.transfer(SenderId(1), ReceiverId(2), Amount(1200), Fee(1.2));