Автор начитался eo и придумал следующий бред
Иммутабельные обьекты это хорошо, но иногда очень хочется иметь мутабельный обьект. Например, в паттерне Builder
, чтобы делать что-то подобное:
new StringBuilder()
.add("a")
.add("b")
.add("c")
или что-нибудь более осмысленное:
new Graph()
.vertices(10)
.edges(edgesList)
.directed(false)
а так же в принципе Fluent interface, используемый не только для паттерна Builder
:
ids
.map(getUserByID)
.map(User.getName)
.mapConcat(User.getFriends)
.filter(me.isFriend)
С мутабельными обьектами все просто. Сделаем пример на основе StringBuilder
с одним методом add, для других случаев все аналогично:
class StringBuilder {
val text = "";
constructor() {}
StringBuilder add(String value) {
text += value;
return this;
}
}
Как сделать это мутабельным? На самом деле мы можем вынести изменение состояния билдера в отдельную чистую функцию, которая будет эндоморфизмом StringBuilder -> StringBuilder:
class StringBuilder {
val text;
constructor(text) {
this.text = text;
}
String getText() {
return this.text;
}
}
StringBuilder addTo(StringBuilder sb, String value) {
return StringBuilder(sb.getText() + value);
}
}
Теперь StringBuilder стал иммутабельным. Однако решив одну проблему мы получили три:
- StringBuilder стал эквивалентен классу String, а значит не нужен
- потеряли Fluent interface
- теперь используется чистая функция, которая не очень хорошо вписывается в ООП
Зато мы получили изменение состояния с помощью наращивания списка эндоморфизмов и можем гордиться, что функциональное программирование хоть где-то пригодилось.
Но мы тут занимаемся ООП, а не ФП, поэтому вспоминаем( https://vk.com/wall-187839235_972 ), что эндоморфизм есть декоратор, поэтому делаем декоратор для добавления строки в StringBuilder (см. диаграмму 1). Думаю реализация метода getText в обоих случаях очевидна. Класс StringBuilderAdded является тем самым декоратором. Теперь построение осуществляется как-то так:
new StringBuilderAdded(
new StringBuilderAdded(
new StringBuilderAdded(
new StringBuilderSimple(""), "a"
), "b"
), "c"
);
Что выглядит как рекурсия, которую не очень удобно писать и напрашивается 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-ами.
P.P.S. Если поехало форматирование, я не виноват.
