Как стать автором
Обновить

Про ООП через призму косвенности. И «Галя, отмена!»

Время на прочтение5 мин
Количество просмотров4.5K

Введение

Да, мы все знаем, что это такое из первых двух статей по запросу "Что такое ООП?" или из потоковых лекций первых семестров ВУЗа.

Казалось бы, ООП – Объектно ориентированное программирование. Там что-то про классы, что-то про объекты, если повезет, то, возможно, вспомним, что такое абстракция, инкапсуляция, наследование и полиморфизм. Что там еще надо знать?
И хорошо, если ты прочитал нормальную статью, и не будешь объяснять инкапсуляцию вот так: "ну это история про private, данные скрываются!".

Уточним формальности

Что вообще такое объектно-ориентированное программирование? Тут все просто, это значит, что ты строишь программное обеспечение из объектов. Илья, ты что гугл, который на запрос "Кто такой опричник?" отвечает "Тот, кто состоял в рядах опричнины"?
Расслабься, объекты – это такие маленькие машинки, которые живут в компьютере и болтают между собой для выполнения работы.

Прежде чем мы реально поймем зачем ООП, надо разобраться с самым важным – с термином indirection (косвенность). Это ключевой принцип ООП.

Вся суть в косвенности

“All problems in computer science can be solved by another level of indirection.” – David Wheeler

“Most performance problems in computer science can be solved by removing a layer of indirection” – [unknown]

Представь, что ты собираешься позвонить другу и рассказать ему про ООП, но ты не знаешь его номера телефона, ты можешь просто посмотреть его в контактах. Это и есть косвенность.
Тут уже стало понятнее, косвенность означает следующее: вместо того, чтобы использовать значение непосредственно в коде, используй указатель на него. Уже что-то вырисовывается про классы, да?

А теперь представь, что ты купил маме новый чайник, твоя мама живет в городе N, а ты живешь в городе M. Но ты знаешь, что твой друг поедет через 3 дня поездом "город M -> город N". И о чудо! Этот друг живет в квартире напротив твоей мамы. Осталось просто отдать ему чайник и вежливо попросить передать его маме.

Собственно, вот и другое значение косвенности – вместо того, чтобы делать работу самому, просто попроси сделать ее кого-то другого ?

У косвенности есть уровни. Тут тоже все просто, ты пришел в магазин, тебе пробили товар с желтым ценником по его обычной цене, ты говоришь, кассиру (layer 1), чтобы убрал этот товар, а кассир, в свою очередь, ведет тебя к Гале, которая умеет делать отмену (layer 2). В этом плане компьютер терпеливый, его можно гонять из места в место в поисках нужной инстанции :)

Переменные и косвенность

Внимательный читатель уже понял, что использовал косвенность уже десятки тысяч раз. А кто не понял, тот поймет.

Есть у нас вот такая функция, где мы гордо говорим про числа от 1 до 5, а потом печатаем их.

int main(int argc, const char *argv[]) {
    NSLog(@"The numbers from 1 to 5:");
    for (int i = 1; i <= 5; i++) {
        NSLog(@"%d\n", i);
    }
    return 0;
}

К нам приходят и говорят, что теперь надо печатать числа от 1 до 10. Лезем в код, меняем в двух местах пятерку на десятку.
Потом к нам приходят и говорят, что теперь надо от 1 до 100, мы устаем и применяем новый изученный скилл – косвенность – выносим информацию о верхней границе в отдельную переменную count.

int main(int argc, const char * argv[]){
    int count = 100;
    NSLog(@"The numbers from 1 to %d:", count);
    for (int i = 1; i <= count; i++) {
        NSLog(@"%d\n", i);
    }
    return 0;
}

Теперь код надо трогать только в одном месте. Стало намного лучше.

Косвенность через файлы

Вы с ребятами забацали новый взрывной стартап – вы запускаете сервис, который считает сколько в строке символов. Осталось только найти инвесторов!

Первая версия выглядит следующим образом:

int main(int argc, const char * argv[]) {
    const char *strs[4] = { "Objective-C", "Swift", "C++", "Go" };
    int strsCount = 4;
    for (int i = 0; i < strsCount; i++) {
        NSLog(@"%s is %lu characters long", strs[i], strlen(strs[i]));
    }
    return 0;
}

Приходят венчурные капиталисты и говорят, что наш стартап будет идеальным для киберспорта, они предлагают узнавать сколько символов в строке формата:

Имя "никнейм" Фамилия

Понимаете к чему это сводится? strs будет выглядеть следующим образом:

const char *strs[4] = {
    "Alexander \"TORONTOTOKYO\" Khertek",
    "Ilya \"Yatoro\" Mularchuk",
    "Danil \"Dendi\" Ishutin",
    "Ilya \"ALOHADANCE\" Korobkin" };

Извините меня, это плохо читаемо, здесь легко ошибиться, трудно что-то исправить, а добавить условно еще 20 игроков – довольно большой объем работы...

А если венчурным капиталистам захочется считать сколько символов в строке формата:

Блюдо "Салат "Альбина" Очень вкусно!". Время приготовления – "35 минут".

"Блюдо \"Салат \"Альбина\" Очень вкусно!\". Время приготовления – \"35 минут\".

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

FILE *wordFile = fopen("/tmp/words.txt", "r");

Проблему это решит, но мы помним – "вместо того, чтобы использовать значение непосредственно в коде, используй указатель на него", поэтому сделаем еще лучше:

int main(int argc, const char * argv[]) {
    if (argc == 1) {
        NSLog(@"you need to provide a file name");
        return 1;
    }
    FILE *strsFile = fopen(argv[1], "r");
    char str[100];
    while (fgets(str, 100, strsFile)) {
        str[strlen(str) - 1] = '\0';
        NSLog(@"%s is %lu characters long", str, strlen(str));
    }
    fclose(strsFile);
    return 0;
}

Теперь можно создать кучу файлов по каждому запросу инвесторов: и киберспорт, и кулинария, и названия таблеток, и т.д.

Теперь программист должен сделать только что-то подобное:

$ clang -framework Foundation -o charactersCounter ./main.m

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

А потом просто дергать исполняемый файл

$ ./charactersCounter /tmp/cyber.txt

Косвенность и ООП

Пора бы и к выводам перейти.

Объектно-ориентированное программирование - это все про косвенность.

ООП использует косвенность для доступа к данным, так же как мы делали это в предыдущих примерах, используя переменные, файлы и аргументы.

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

Все остальное – это side effect of indirection. Хотя это и очень громкие слова, поэтому статья и называется "Про ООП через призму косвенности".

Абстракция реализуется с помощью косвенности.

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

Чтобы добавить уровень абстракции, необходимо добавить уровень косвенности. Но добавление косвенности не обязательно дает абстракцию. Обычные необходимое и достаточное условия.

Инкапсуляция — собирание в одном месте знания, относящиеся к устройству некой сущности, правилам обращения и операциям с ней. Или организация слоев косвенности таким образом, чтобы собрать в "капсулу" все знания о той или иной сущности. Та самая Галя, которая умеет делать отмену – только она знает какой пин-код ввести, и никому она этот пин-код не расскажет, но Галю можно попросить прийти и ввести его.

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

И снова слои, и снова объясню на Гале. Тут совсем просто

class GalyaCashier: DefaultCashier

То есть Галя умеет делать все, что умеют обычные кассиры, но moreover Галя умеет отмену.

Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

А тут на Гале понять можно? Конечно)

Представим, что в конце дня каждый сотрудник магазина должен написать отчет. Т.е. – есть какой-то интерфейс с методом writeReport()

Только вот DefaultCashier будет писать:
"За сегодня я пробил 120 товаров"

А GalyaCashier будет писать:

"За сегодня я пробила 100 товаров и сделала 3 отмены"

Достаточно заменить в вашей системе объект одного класса на объект другого класса, и результат будет достигнут.


Заключение

Спасибо большое всем за внимание! Вообще каждый в праве понимать как хочет, но именно это объяснение запало мне в душу. Все совпадения с Галями случайны, я очень благодарен за то, что это стало для меня синонимом к "слой косвенности".

Источники

Learn Objective-C on the Mac, Authors: Mark Dalrymple, Scott Knaster

https://medium.com/@nmckinnonblog/indirection-fba1857630e2

https://softwareengineering.stackexchange.com/questions/111756/what-is-the-difference-between-layer-of-abstraction-and-level-of-indirection

https://habr.com/ru/post/435900/

https://habr.com/ru/post/87205/

Теги:
Хабы:
Всего голосов 10: ↑7 и ↓3+4
Комментарии9

Публикации