Для начала рассмотрим некоторые базовые идеи программирования, которые часто отождествляют с ООП (что бы это ни значило). Некоторые из этих идей существовали не только задолго до появления термина ООП, но и задолго до появления компьютеров как таковых.

Абстракция

В далекой далекой галактике древности, у людей не существовало цифр отдельно от вещей. Два оленя или две палки обозначались отдельными словами и знаками. Так продолжалось до тех пор, пока кто-то не увидел, что можно выделить понятия "два", "три", "десять" и начать применять их ко всем перечислимым предметам. Так появилась абстракция "числа". Мы настолько привыкли к этому, что даже не задумываемся над их абстрактностью, а вот для древнего человека это был прорыв.

В наших программах, вообще, все с чем мы работаем - это абстракция. Более того, я возьмусь утверждать, что большая часть задач, которые решаются с помощью программирования, сводится к тому, чтобы создавать и использовать правильные абстракции. Под словом "правильные" я, ни в коем случае не имею в виду "много". Думаю, здесь собрались взрослые люди, которым не надо объяснять что мир не черный и белый, а во всем хороша золотая середина.

Перейдем от абстрактных рассуждений к конкретным примерам. Самое простое, что можно придумать, это функция.

Абстракция с помощью функций

Попробуйте с трех раз угадать, что происходит в этом коде:

const a = 5;
const b = 10;
const result = a * b;

Вероятно, вы ответите, что это умножение двух чисел, но я туда заложил другой смысл, а именно нахождение площади прямоугольника. И если в данной ситуации, об этом можно было бы догадаться, назови я переменную result именем area , то в более сложных ситуациях (даже в этой, но внутри большого блока кода) шансов просто нет.

Очевидно, что можно реализовать функцию и сделать код таким:

const a = 5;
const b = 10;
const area = getRectangleArea(a, b);

Обратите внимание на важнейшую идею абстракции. Первостепенно не сокращение дублирования кода, а создание кода, который выражает ваши мысли прямо. Если нам нужно найти площадь прямоугольника, то именно это мы и делаем в коде. В 2013 году, я придумал специальный термин "ментальное программирование" с таким определением:

Ментальное программирование это написание такого кода, читая который, можно восстановить исходную задачу в первоначальных терминах

Функция как абстракция довольно понятная вещь, особенно приятно если она чистая. А что насчет данных?

Абстракция с помощью Данных

Чтобы не ходить далеко, разберем пример используемый в курсе на хекслете. Опишем код, который позволяет работать с отрезками на координатной плоскости.

const segment1 = [1, 10, 5, 2];
const segment2 = [-5, 0, 9, 10];
const startPoint1 = getStartPoint(segment1); // [1, 10]
const crossed = hasIntersection(segment1, segment2);
const rotatedSegment = rotate(10, segment1);

В этом коде видно применение функций, что резко облегчает его понимание и это хорошо, но кое-что по прежнему очень плохо. Структура отрезка торчит наружу, следовательно если захочется поменять реализацию, то придется найти все места где создаются сегменты и переписать их. В примере выше, скорее всего, захочется изменить структуру на подобную: [[1, 10], [5, 2]] . То есть здесь мы видим явное выделение понятия "точка". В свою очередь, для точек можно ввести собственную абстракцию, что упростит реализацию функций сегментов, так как они будут опираться на функции для работы с точками. Эта концепция носит название "барьеры абстракции".

Так как же правильно создавать сегменты? И снова нам помогают функции:

const segment1 = makeSegment(1, 10, 5, 2);
// Вариант с точками
const segment2 = makeSegmentWithPoints(makePoint(1, 10), makePoint(5, 2));

У нас есть конструктор makeSegment , так же у нас есть селекторы (геттеры) getStartPoint и getFinishPoint. Напоминает что-нибудь? Получилось, что мы реализовали придуманный нами АТД.

Возникает закономерный вопрос: а что будет, если кто-то поправит данные напрямую, в обход интерфейсных функций?

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

// Определения

const makeSegment = (point1, point2) => (message) => {
  switch (message) {
    case 'getStartPoint':
      return point1;
    case 'getFinishPoint':
      return point2;
    default:
      // some handling
  }
};

const getStartPoint = segment => segment('getStartPoint');

// Использование

const segment = makeSegment(makePoint(1, 10), makePoint(5, 2));
console.log(getStartPoint(segment)); // [1, 10]

Вероятно, для многих (кто не учится на хекслете, хехе) этот код выглядит шокирующе, хотя в реальности здесь используется только свойство замыкания, и наши данные представлены функциями (!!!). Поэкспериментируйте на repl.it , чтобы хорошо разобраться в этом коде.

Теперь добраться до кишок можно только с помощью посылки сообщения. Напрямую воздействовать на составные части нашего сегмента просто невозможно. Причем для этого не использовались специализированные средства, все получилось на одних функциях.

В итоге мы получили хорошо защищенную абстракцию, которую легко расширять и которой легко пользоваться. Она моделирует заданную предметную область, и говорим мы о сегментах в терминах сегментов, а не концепций языков программирования.

Выше я написал, что в коде используется "посылка сообщений", но не дал объяснения. Исправляюсь. Как правило, в языках вызов конкретной функции - это прерогатива языка, если функция определена, то она будет вызвана, если нет, то возникнет ошибка. В случае с посылкой сообщения ответственность за реакцию ложится на приемник. Технически есть разные способы реализации, один из которых показан выше. Функция принимает на вход строчку-сообщение, и внутри решает как отреагировать на основе конструкции switch. Важно то, что можно определить какое-то дефолтное поведение, более того, можно строить динамические сообщения и, даже менять состав ожидаемых сообщений после ответа на предыдущее. Но не все языки предоставляют такую возможность: например в java нет посылки сообщений (но можно эмулировать используя reflection), в php и ruby есть, а в js они реализуются с помощью механизма Proxy.

Как видите ООП нет (а может есть? ;), а многие идеи, которые постоянно приписывают этому трехбуквию, - есть.


Вопросы на самопроверку

Что означает фраза применительно к ОС семейства *NIX:

Все есть файл

results matching ""

    No results matching ""