0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Подход, который мы разобрали, нам позволяет строить Fluent Interface и делать различные DSL. Но в этом подходе есть недостатки, некоторые из которых очень важны.</p>
1
<p>Подход, который мы разобрали, нам позволяет строить Fluent Interface и делать различные DSL. Но в этом подходе есть недостатки, некоторые из которых очень важны.</p>
2
<p>В этом уроке мы рассмотрим одну важную особенность - "неизменяемость". Она нужна, чтобы исправлять недостатки реализации Fluent Interface.</p>
2
<p>В этом уроке мы рассмотрим одну важную особенность - "неизменяемость". Она нужна, чтобы исправлять недостатки реализации Fluent Interface.</p>
3
<h2>Неизменяемый объект</h2>
3
<h2>Неизменяемый объект</h2>
4
<p>Допустим, у нас есть коллекция машин, которые мы оборачиваем в Enumerable, и делаем из нее выборку:</p>
4
<p>Допустим, у нас есть коллекция машин, которые мы оборачиваем в Enumerable, и делаем из нее выборку:</p>
5
<p>В этом примере нам нужны только те машины, которые производились после 2012 года. При этом мы хотим не просто выбрать машины одного бренда, а показать пользователям разницу между тем, какие есть машины разных брендов. Например, мы хотим показать отдельно Kia и BMW. Причем нам нужны обе модели.</p>
5
<p>В этом примере нам нужны только те машины, которые производились после 2012 года. При этом мы хотим не просто выбрать машины одного бренда, а показать пользователям разницу между тем, какие есть машины разных брендов. Например, мы хотим показать отдельно Kia и BMW. Причем нам нужны обе модели.</p>
6
<p>Если сначала сделать выборку машин Kia, и после этого сделать из этой же коллекции выборку BMW машин, то окажется, что в коллекции нет машин BMW.</p>
6
<p>Если сначала сделать выборку машин Kia, и после этого сделать из этой же коллекции выборку BMW машин, то окажется, что в коллекции нет машин BMW.</p>
7
<p>Зная, как работает код, нам это не покажется странным, а пользователям этой библиотеки - покажется. Кроме того, это крайне неудобно.</p>
7
<p>Зная, как работает код, нам это не покажется странным, а пользователям этой библиотеки - покажется. Кроме того, это крайне неудобно.</p>
8
<p>Получается, что когда мы вызываем where, то идет внутренняя замена и изменение объекта. Это означает, что coll, с которым мы работаем, уже изменен. Также внутри уже выбраны только те машины, которые относятся к бренду Kia. В итоге в последней строчке у нас ничего не может быть выбрано кроме машин Kia.</p>
8
<p>Получается, что когда мы вызываем where, то идет внутренняя замена и изменение объекта. Это означает, что coll, с которым мы работаем, уже изменен. Также внутри уже выбраны только те машины, которые относятся к бренду Kia. В итоге в последней строчке у нас ничего не может быть выбрано кроме машин Kia.</p>
9
<p>Это неудобно и плохо. В подобной задаче нужно понять, как сделать общую выборку, задать общие параметры и потом выбирать разные подмножества изначального множества. При этом это нужно делать одновременно в одном и том же участке кода.</p>
9
<p>Это неудобно и плохо. В подобной задаче нужно понять, как сделать общую выборку, задать общие параметры и потом выбирать разные подмножества изначального множества. При этом это нужно делать одновременно в одном и том же участке кода.</p>
10
<p>Чтобы добиться этого, нужно сделать наш объект неизменяемым. Дело в том, что состояние приводит к проблемам, и чаще всего всё крутится вокруг него. И именно изменяемое состояние приводит к таким проблемам, с которыми нужно бороться.</p>
10
<p>Чтобы добиться этого, нужно сделать наш объект неизменяемым. Дело в том, что состояние приводит к проблемам, и чаще всего всё крутится вокруг него. И именно изменяемое состояние приводит к таким проблемам, с которыми нужно бороться.</p>
11
<h2>Неизменяемость</h2>
11
<h2>Неизменяемость</h2>
12
<p>Существует два подхода в работе с изменениями:</p>
12
<p>Существует два подхода в работе с изменениями:</p>
13
<ul><li>Императивный подход - возвращается измененная структура данных</li>
13
<ul><li>Императивный подход - возвращается измененная структура данных</li>
14
<li>Функциональный подход - возвращается новая структура данных, которая является преобразованием старой</li>
14
<li>Функциональный подход - возвращается новая структура данных, которая является преобразованием старой</li>
15
</ul><p>Вернемся к тому же коду:</p>
15
</ul><p>Вернемся к тому же коду:</p>
16
<p>В этом случае мы создаем coll, из которого делаем выборку и сохраняем ее в coll2. Далее делаем where, где car.brand === 'kia', и where, где car.brand === bmw. В итоге BMW был выбран без учета предыдущей выборки. То есть строчка coll.where((car) => car.brand === 'kia'); // 1 не повлияла на следующую строчку.</p>
16
<p>В этом случае мы создаем coll, из которого делаем выборку и сохраняем ее в coll2. Далее делаем where, где car.brand === 'kia', и where, где car.brand === bmw. В итоге BMW был выбран без учета предыдущей выборки. То есть строчка coll.where((car) => car.brand === 'kia'); // 1 не повлияла на следующую строчку.</p>
17
<p>На самом деле здесь не происходят изменения. Каждая выборка, каждый вызов новой функции и нового метода, который идет после точки, возвращает нечто новое. В итоге это не изменяет предыдущее состояние.</p>
17
<p>На самом деле здесь не происходят изменения. Каждая выборка, каждый вызов новой функции и нового метода, который идет после точки, возвращает нечто новое. В итоге это не изменяет предыдущее состояние.</p>
18
<p>Теперь мы можем делать одно большое подможество, выделять из него мелкие подможества и работать с ними одновременно. При этом не нужно бояться, что можно что-то сломать.</p>
18
<p>Теперь мы можем делать одно большое подможество, выделять из него мелкие подможества и работать с ними одновременно. При этом не нужно бояться, что можно что-то сломать.</p>
19
<h2>Тесты</h2>
19
<h2>Тесты</h2>
20
<p>Тестировать эту вещь достаточно просто:</p>
20
<p>Тестировать эту вещь достаточно просто:</p>
21
<p>В этом примере при проверке участвует не coll, а result.</p>
21
<p>В этом примере при проверке участвует не coll, а result.</p>
22
<p>Если бы у нас была изменяемость, то orderBy изменил бы сам coll. В итоге наши машины шли бы не в том порядке. А чтобы добиться неизменяемости, нужно применить подход, который описали выше. То есть мы делаем изменение coll, а на result это не должно сказаться, потому что это разные объекты.</p>
22
<p>Если бы у нас была изменяемость, то orderBy изменил бы сам coll. В итоге наши машины шли бы не в том порядке. А чтобы добиться неизменяемости, нужно применить подход, который описали выше. То есть мы делаем изменение coll, а на result это не должно сказаться, потому что это разные объекты.</p>
23
<p>Когда мы делаем result.toArray, мы получаем машины в том порядке, в котором они появились у нас в выборке. В итоге у нас будут элементы под индексами 2 и 4 - 2014 и 2012 года.</p>
23
<p>Когда мы делаем result.toArray, мы получаем машины в том порядке, в котором они появились у нас в выборке. В итоге у нас будут элементы под индексами 2 и 4 - 2014 и 2012 года.</p>
24
<p>Теперь разберемся, как реализовать код, чтобы этот тест прошел.</p>
24
<p>Теперь разберемся, как реализовать код, чтобы этот тест прошел.</p>
25
<h2>Реализация</h2>
25
<h2>Реализация</h2>
26
<p>В реализации практически ничего не меняется, только в функциях обработчиков мы не меняем this.collection:</p>
26
<p>В реализации практически ничего не меняется, только в функциях обработчиков мы не меняем this.collection:</p>
27
<p>Здесь мы применяем функции фильтрации или другую функцию высшего порядка. Фактически они всегда возвращают новую коллекцию и никогда не изменяют старую. В итоге мы возвращаем новый Enumerable.</p>
27
<p>Здесь мы применяем функции фильтрации или другую функцию высшего порядка. Фактически они всегда возвращают новую коллекцию и никогда не изменяют старую. В итоге мы возвращаем новый Enumerable.</p>
28
<p>Каждый раз, когда мы вызываем любой метод, определенный в Enumerable, он делает обработку коллекции и возвращает новый Enumerable, в котором уже лежит какое-то подмножество.</p>
28
<p>Каждый раз, когда мы вызываем любой метод, определенный в Enumerable, он делает обработку коллекции и возвращает новый Enumerable, в котором уже лежит какое-то подмножество.</p>
29
<p>Получается, что если немного изменить код, мы получим большое количество преимуществ.</p>
29
<p>Получается, что если немного изменить код, мы получим большое количество преимуществ.</p>
30
<h2>In-Place замена</h2>
30
<h2>In-Place замена</h2>
31
<p>Не все функции работают так, как мы описали выше. Например, это касается функции sort почти во всех языках программирования:</p>
31
<p>Не все функции работают так, как мы описали выше. Например, это касается функции sort почти во всех языках программирования:</p>
32
<p>sort сделан так, что он меняет исходную сущность. В нашем примере мы вызвали sort, и если мы распечатаем массив, то получаем 1, 3, 5.</p>
32
<p>sort сделан так, что он меняет исходную сущность. В нашем примере мы вызвали sort, и если мы распечатаем массив, то получаем 1, 3, 5.</p>
33
<p>То есть sort не возвращает новый массив. Сам исходный массив меняется - то есть это тот же массив, но порядок уже другой. В случае нашей коллекции это плохо, потому что мы не сможем делать неизменяемые коллекции и возвращать новые.</p>
33
<p>То есть sort не возвращает новый массив. Сам исходный массив меняется - то есть это тот же массив, но порядок уже другой. В случае нашей коллекции это плохо, потому что мы не сможем делать неизменяемые коллекции и возвращать новые.</p>
34
<p>Это можно обойти. Для этого существует специальный метод, который есть у массивов. Он называется slice и позволяет создать копию массива. После этого мы можем сделать с ним всё что угодно:</p>
34
<p>Это можно обойти. Для этого существует специальный метод, который есть у массивов. Он называется slice и позволяет создать копию массива. После этого мы можем сделать с ним всё что угодно:</p>
35
<p>Здесь видно, как мы с помощью Fluent Interface на массивах получаем slice - срез. Но поскольку мы не указываем параметров, мы получаем копию всего массива. Далее мы сортируем эту копию, после чего sort возвращает эту же копию, с которой мы можем продолжать работать.</p>
35
<p>Здесь видно, как мы с помощью Fluent Interface на массивах получаем slice - срез. Но поскольку мы не указываем параметров, мы получаем копию всего массива. Далее мы сортируем эту копию, после чего sort возвращает эту же копию, с которой мы можем продолжать работать.</p>
36
<p>Если мы распечатаем исходный массив, то увидим, что его внутреннее состояние не изменилось, это по-прежнему те же 5, 1, 3.</p>
36
<p>Если мы распечатаем исходный массив, то увидим, что его внутреннее состояние не изменилось, это по-прежнему те же 5, 1, 3.</p>
37
<h2>Выводы</h2>
37
<h2>Выводы</h2>
38
<p>В этом уроке мы рассмотрели одну важную особенность - "неизменяемость". Теперь мы знаем, что она поможет исправлять недостатки реализации Fluent Interface. Также мы научились тестировать код на неизменяемость и реализовывать ее.</p>
38
<p>В этом уроке мы рассмотрели одну важную особенность - "неизменяемость". Теперь мы знаем, что она поможет исправлять недостатки реализации Fluent Interface. Также мы научились тестировать код на неизменяемость и реализовывать ее.</p>