Как сделать это мутабельным? На самом деле мы можем вынести изменение состояния билдера в отдельную чистую функцию, которая будет эндоморфизмом StringBuilder -> StringBuilder:
Теперь StringBuilder стал иммутабельным. Однако решив одну проблему мы получили три:
- StringBuilder стал эквивалентен классу String, а значит не нужен
- потеряли Fluent interface
- теперь используется чистая функция, которая не очень хорошо вписывается в ООП
Зато мы получили изменение состояния с помощью наращивания списка эндоморфизмов и можем гордиться, что функциональное программирование хоть где-то пригодилось.
Но мы тут занимаемся ООП, а не ФП, поэтому вспоминаем( https://vk.com/wall-187839235_972 ), что эндоморфизм есть декоратор, поэтому делаем декоратор для добавления строки в StringBuilder (см. диаграмму 1). Думаю реализация метода getText в обоих случаях очевидна. Класс StringBuilderAdded является тем самым декоратором. Теперь построение осуществляется как-то так:
Что выглядит как рекурсия, которую не очень удобно писать и напрашивается do-нотация (если уж не reduce с списком конструкторов декораторов), но мы отгоняем от себя мысли все сделать функциональным и вспоминаем, что мы решили две проблемы из трех. Остался Fluent interface. Очевидно, что самое место ему в StringBuilder, чтобы им можно было пользоваться, как было описано в начале поста, поэтому следующая реализация будет такой, как на диаграмме 2. Реализация отличается тем, что вместо рекурсивной вложенности, как выше, теперь построение StringBuilderAdded помещается в реализации add. Теперь все идеально: мы получили иммутабельные обьекты с Fluent interface-ом и полностью в ООП-е.
Что еще можно сказать, так это то, что кажется, что такая реализация неэффективна: создается много обьектов и при вызове getText будет длинная рекурсия. С этим, кажется, что несложно справиться, сделав более эффективную реализацию декоратора, который использует какой-нибудь Visitor с более быстрым соединением строк и без рекурсии или возвращать не String, а поток символов и соединять потоки символов. В общем, проблема решается без ломания полученной парадигмы.
P.S. Кстати похожая штука с эндоморфизмами используется в библиотеке Redux, которая получила название от известного катаморфизма reduce, суть которого в том, что
reduce(init = StringBuilder(), list = [addEndomorphisms])
дает изменение состояния StringBuilder до нужного. Только в Redux вместо StringBuilder-а меняется глобальное состояние приложения и эндоморфизмы называются reducer-ами.