HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <h2>Абстракция</h2>
1 <h2>Абстракция</h2>
2 <p>Пары отлично подходят для представления рациональных чисел. Первый элемент пары может выступать числителем, а второй элемент - знаменателем.</p>
2 <p>Пары отлично подходят для представления рациональных чисел. Первый элемент пары может выступать числителем, а второй элемент - знаменателем.</p>
3 <p>a / b → (a, b)</p>
3 <p>a / b → (a, b)</p>
4 <p>Мы увидели, как пары могут использоваться для представления разных структур, а сочетания таких представлений - для создания новых структур.</p>
4 <p>Мы увидели, как пары могут использоваться для представления разных структур, а сочетания таких представлений - для создания новых структур.</p>
5 <h2>Рациональные числа</h2>
5 <h2>Рациональные числа</h2>
6 <p>Еще одной очень простой и интересной абстракцией, помимо графических примитивов, являются так называемые рациональные числа. С рациональными числами знакомы все. Рациональным называют число, которое можно представить в виде обыкновенной дроби. Обыкновенная дробь состоит из двух чисел - числителя и знаменателя, поэтому эта абстракция идеально ложится на пары. Интерфейс нам уже знаком и состоит из конструктора и двух селекторов:</p>
6 <p>Еще одной очень простой и интересной абстракцией, помимо графических примитивов, являются так называемые рациональные числа. С рациональными числами знакомы все. Рациональным называют число, которое можно представить в виде обыкновенной дроби. Обыкновенная дробь состоит из двух чисел - числителя и знаменателя, поэтому эта абстракция идеально ложится на пары. Интерфейс нам уже знаком и состоит из конструктора и двух селекторов:</p>
7 <p>Конструктор make() принимает два числа. Селектор numer() ответственен за получение числителя, denom() - знаменателя.</p>
7 <p>Конструктор make() принимает два числа. Селектор numer() ответственен за получение числителя, denom() - знаменателя.</p>
8 <p>На рациональных числах определены различные операции, например, сложение рациональных чисел, умножение - все это происходит по определенным правилам:</p>
8 <p>На рациональных числах определены различные операции, например, сложение рациональных чисел, умножение - все это происходит по определенным правилам:</p>
9 <p>А по такой формуле работает функция is_equal(), которая проверяет равенство дробей:</p>
9 <p>А по такой формуле работает функция is_equal(), которая проверяет равенство дробей:</p>
10 <p>Абстракция позволяет нам делать некоторые интересные вещи так, чтобы остальная часть программы об этом не знала. Например, нормализация дроби:</p>
10 <p>Абстракция позволяет нам делать некоторые интересные вещи так, чтобы остальная часть программы об этом не знала. Например, нормализация дроби:</p>
11 <p>Сложив два числа из примера, мы получим 5/10, что является одним из представлений числа 1/2 или 10/20 (обычно нормализуют в меньшую сторону). Когда мы говорим про работу с абстракцией, у нас есть несколько способов провести нормализацию числа. Например, это можно делать при вызове конструктора:</p>
11 <p>Сложив два числа из примера, мы получим 5/10, что является одним из представлений числа 1/2 или 10/20 (обычно нормализуют в меньшую сторону). Когда мы говорим про работу с абстракцией, у нас есть несколько способов провести нормализацию числа. Например, это можно делать при вызове конструктора:</p>
12 <p>Тогда нормализация будет производиться один раз при создании рационального числа. Другой способ - делать нормализацию при вызове селекторов:</p>
12 <p>Тогда нормализация будет производиться один раз при создании рационального числа. Другой способ - делать нормализацию при вызове селекторов:</p>
13 <p>При этом снаружи будет совершенно все равно, какой способ мы используем. Очевидно, что в данном конкретном случае использование первого способа предпочтительней с точки зрения производительности, потому что селекторы будут вызываться намного чаще конструктора, и производить вычисления каждый раз (а они могут быть довольно объемными) просто неэффективно. Важно понимать на концептуальном уровне, что способ не один, и его выбор зависит от требований к конкретной системе. Поскольку мы построили барьер абстракции, и манипуляции с рациональными числами производятся только через конструктор и селекторы, то оптимизацию можно спокойно отложить на более поздний срок.</p>
13 <p>При этом снаружи будет совершенно все равно, какой способ мы используем. Очевидно, что в данном конкретном случае использование первого способа предпочтительней с точки зрения производительности, потому что селекторы будут вызываться намного чаще конструктора, и производить вычисления каждый раз (а они могут быть довольно объемными) просто неэффективно. Важно понимать на концептуальном уровне, что способ не один, и его выбор зависит от требований к конкретной системе. Поскольку мы построили барьер абстракции, и манипуляции с рациональными числами производятся только через конструктор и селекторы, то оптимизацию можно спокойно отложить на более поздний срок.</p>
14 <p>Есть еще один интересный случай - нормализация знака. Например, при создании рационального числа, в котором числитель или знаменатель отрицателен, нам нужно как-то сохранить информацию об этом, чтобы само рациональное число было отрицательным:</p>
14 <p>Есть еще один интересный случай - нормализация знака. Например, при создании рационального числа, в котором числитель или знаменатель отрицателен, нам нужно как-то сохранить информацию об этом, чтобы само рациональное число было отрицательным:</p>
15 <p>Мы можем договориться, что знак хранится в числителе, и потом этот факт везде использовать. Если отрицательное значение имеет знаменатель, то мы производим нормализацию, перемещая знак в числитель для единообразной работы. Наружу выставляется удобный интерфейс, чтобы пользователю не приходилось задумываться о правильном использовании знака. Всю работу по приведению к нормальной форме мы делаем внутри функции.</p>
15 <p>Мы можем договориться, что знак хранится в числителе, и потом этот факт везде использовать. Если отрицательное значение имеет знаменатель, то мы производим нормализацию, перемещая знак в числитель для единообразной работы. Наружу выставляется удобный интерфейс, чтобы пользователю не приходилось задумываться о правильном использовании знака. Всю работу по приведению к нормальной форме мы делаем внутри функции.</p>
16 <p>При этом есть различные пограничные случаи, например:</p>
16 <p>При этом есть различные пограничные случаи, например:</p>
17 <p>В примере выше и числитель и знаменатель в первом рациональном числе - отрицательные числа. В таком случае два знака - у дроби должны уходить. А значит, функция make() должна знать об этом. Само по себе это не заработает, и вместо правильного результата могут появляться совершенно непредсказуемые эффекты.</p>
17 <p>В примере выше и числитель и знаменатель в первом рациональном числе - отрицательные числа. В таком случае два знака - у дроби должны уходить. А значит, функция make() должна знать об этом. Само по себе это не заработает, и вместо правильного результата могут появляться совершенно непредсказуемые эффекты.</p>
18 <p>Абстракция данных позволяет нам откладывать момент нормализации до той поры, когда нам действительно понадобится такая функциональность. Причем мы можем реализовать его необходимым нам способом. Прикладной код от этого не поменяется - для него реализация значения не имеет.</p>
18 <p>Абстракция данных позволяет нам откладывать момент нормализации до той поры, когда нам действительно понадобится такая функциональность. Причем мы можем реализовать его необходимым нам способом. Прикладной код от этого не поменяется - для него реализация значения не имеет.</p>