Абстракция позволяет нам не думать о деталях реализации и сосредоточиться на её использовании. Более того, при необходимости реализацию абстракции можно всегда переписать, не боясь сломать использующий её код. Но есть ещё одна важная причина, по которой нужно использовать абстракцию — соблюдение инвариантов.

Инвариант в программировании — логическое выражение, определяющее непротиворечивость состояния (набора данных).

Разберёмся на примере. Когда мы описали конструктор и селекторы для рациональных чисел, то неявно подразумевали выполнение следующих инвариантов:

<?php

$num = makeRational($numer, $denom);
$numer == getNumer($num); // true
$denom == getDenom($num); // true

Передав в конструктор рационального числа числитель и знаменатель, мы ожидаем, что получим их (те же числа), если применим селекторы к этому рациональному числу. Именно так определяется корректность работы данной абстракции. Этот код практически является тестами!

Инварианты существуют относительно любой операции. И иногда они довольно хитрые. Например, рациональные числа можно сравнивать между собой, но не прямым способом, потому, что одни и те же дроби можно представлять разными способами: 1/2 и 2/4. Код, который не учитывает этого факта, работает некорректно:

<?php

$num1 = makeRational(2, 4);
$num2 = makeRational(8, 16);

print_r($num1 == $num2); // false

Задача приведения дроби к нормальной форме называется нормализацией. Реализовать её можно разными способами. Самый очевидный — выполнять нормализацию во время создания дроби, внутри функции makeRational. Другой — выполнять нормализацию уже при обращении через функции getNumer и getDenom. Последний способ обладает недостатком — вычисление нормальной формы происходит на каждый вызов. Избежать этого можно, используя технику мемоизации.

Учитывая новые вводные, становится понятно, что инвариант, связывающий конструктор и селекторы, нуждается в модификации. Функции getNumer и getDenom должны вернуть не переданные значения, а значения после нормализации (если дробь уже нормализована, то это будут те же самые значения).

<?php

$num = makeRational(10, 20);
getNumer($num); // 1
getDenom($num); // 2

Становится понятно, что абстракция не только прячет от нас реализацию, но и отвечает за соблюдение инвариантов. Любая работа в обход абстракции чревата тем, что не будут учтены внутренние преобразования:

<?php

// Эти данные не нормализованы, потому что не использовался конструктор
$num = ['numer' => 10, 'denom' => 20];

// Возвращается не то что должно (ожидается нормализованный возврат):
getNumer($num); // 10
getDenom($num); // 20

Многие языки в своём арсенале имеют средства для защиты данных от прямого воздействия, в обход интерфейсных функций. В PHP за это отвечают модификаторы доступа к свойствам объектов public, protected и private, с которыми мы скоро познакомимся.

Защиту данных можно организовать и без специальных средств, только за счёт функций высшего порядка. Данный способ основан на создании абстракций с помощью анонимных функций, замыканий и передачи сообщений (подробнее в SICP). Если вы хотите узнать об этом больше, то попробуйте наш курс PHP: Составные данные

Хочу сразу предостеречь вас от следования культу карго. Несмотря на то, что идея с защитой данных выглядит очень здраво, в реальности подобные механизмы крайне легко обходятся с помощью Reflection API и даже без них, просто за счёт ссылочных данных. Поэтому подобная защита — она больше "от дурака". Второй момент связан с тем, что в мире немало языков (пример, JavaScript), в которых всё нормально с абстракциями, но нет механизмов для защиты данных — и ничего страшного не произошло. Другими словами, на практике, при использовании абстракций, никто особо и не пытается их нарушать специально. И я склоняюсь к мысли, что значение принудительной защиты данных сильно преувеличено.

Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →