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>