HTML Diff
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) =&gt; car.brand === 'kia'); // 1 не повлияла на следующую строчку.</p>
16 <p>В этом случае мы создаем coll, из которого делаем выборку и сохраняем ее в coll2. Далее делаем where, где car.brand === 'kia', и where, где car.brand === bmw. В итоге BMW был выбран без учета предыдущей выборки. То есть строчка coll.where((car) =&gt; 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>