Pull to refresh

Полиморфизм, шмолиморфизм…

Reading time6 min
Views15K
Я думаю всем уже окончательно заморочили голову бесконечные статьи про полиморфизм, но меня они достали тоже и я решил… написать свою.

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

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

Базовые концепции нужно уметь объяснять без привлечения языков программирования вообще! Второй этап — показать как базове концепции используются в том или ином языке программирования. Но это — уже другая история.

Итак. Давайте попробуем рассказать человеку программисту не знающему ни одного, известного вам, языка программирования (ну например он всю жизнь использовал только FORTRAN-66) о полиморфизме.

Начнём сначала. С объектов. Что такое объект в ООП? Это абстракция, позволяющая описывать большие программы. Мы группируем данные и код обработки этих данных в некоторую сущность под названием «объект». Зачем мы это делаем? Ну… чтобы программа казалась выглядящей проще. Человеку свойственно антропоморфизировать всё подряд (не верите — посмотрите мультфильмы) и ему кажется что программа составленная из таких маленьких «человечков» будет понятнее чем миллион не связанных друг с другом формул. Конечно когда мы пишем программы мы не моделируем человека достаточно подробно: если наш маленький человечек призван следить за состоянием принтера или банковского счёта, то нам, в общем, всё равно какого цвета у него глаза и даже не важно — какого пола этот воображаемый человечек. Мы наделяем его только самой рудиментарной памятью (скажем человечек, обслуживающий принтер может помнить сколько на этом принтере было распечатано страниц, а человечек, обслуживающий банкоский счёт будет помнить сколько на этом счету денег) и беспокоимся только о паре элементарных операций (скажем «принтерный человечек» может распечатать на бумаге текст — или вернуть ошибку если у принтера кончилась бумага, а «счетоводный» человечек может снять денег со счёта — или вернуть ошибку если их там нет).

Отлично. Теперь мы знаем что такое объект (знаем понятие, но не его реализацию в каком-либо языке программирования — хотя я уверен человек работавший 40 лет с FORTRAN66 выдаст вам десяток способов реализации подобной концепции). Пойдём дальше. Разумеется наш человечек будет бороться за своё рабочее место и на попылки «залезть к нему в стол» и изменить количество денег на счету будет обижаться и либо не давать это сделать, либо хадрить и плохо работать впоследствии. Этот принцип называется «инкапсуляцией»: за своё состояние каждый человечек отвечает сам. Иногда этот принцип полезно нарушить: скажем аудитору (в программировании обычно называемому инспектором) бывает полезно предоставить исключительные права для выполнения его работы, но если он ими злоупотребит (скажем снимет все деньги со счёта и сбежит), то вся программа может рухнуть.

Дальше. Полиморфизм. Обычно вторым руководящим принципом ООП считается наследование, но на самом деле полиморфизм — вещь куда более простая. Этот принцип просто-напросто гласит что резюме работника не совпадает с самим работником. Скажем если писатель нанимает человека, который будет записывать его гениальные мысли — то его не волнует как именно «писарь» это будет делать: стенографируя на бумаге, используя печатную машинку или высекая текст на камне. Главное чтобы он это делал достаточно быстро. Конечно это может интересовать компанию, с которой у писателя договор (то есть программиста, пишущего программу) ибо лист бумаги дешевле, чем обработанный камень. Но это уже другая история. Есть языки со статической реализацией когда резюме и список умений работника должны существовать явно и их соответствие проверяется ещё до того, как фабрика будет построена и динамические языки — там и формальное понятие «резюме» может отсуствовать! Хотя обычно у программиста, пишущего программу, какие-то резюме на листках бумаги всё равно образуются.

Замечательно. Что же такое наследование? Ну тут тоже никаких секретов нет: не всегда удобно готовить себе работника «с детского сада» (то есть писать класс/прототип/etc с нуля). Иногда проще взять уже готового работника и доучить его немного. Скажем если нашему гениальному писателю потребуется человек, который может не только записывать гениальные мысли, но и зарисовывать дизайны замков, в которых происходит действие то может оказаться лучше взять какого-нибудь писаря и научить его рисовать оные замки. Правда тут могут оказаться неожиданности. Выяснится что писарь имеет какие-то свои «тараканы в голове». Ну например по команде «взять в ручи руки ручку и подготовиться к письму» он берёт ещё и чистый лист бумаги и разлиновывает его — а на планах замков такие линейки нежелательны. Чем больше циклов переучивания (чем «выше» дерево наследования) — тем больше шансов на появление странных накладок. В идеале было бы хорошо использовать только «специально обученных работников», которых отбирают по резюме — но это может оказаться очень дорого. Научить токаря протирать детали спецраствором может оказаться гораздо проще, чем создать нового специалиста, который умеет и точить детали и протирать их тряпочкой.

На закуску. Что такое абстрактный класс? Это класс (класс в школьном смысле — место, где людей чему-то учат), выпускающий полуфабрикат. Не специалистов, а недоучившихся специалистов. Он учился-учился, но научился только включать-выключать станок. А вот обрабатывать детали (точить их или фрезеровать) — он ещё не умеет. Большинство языков программирования (разумеется только тех где есть формальные резюме) позволяют описывать только «классы для подготовки» таких специалистов. Выпустить недоспециалиста нельзя, но можно добавить ещё один класс в «школу подготовки молодого бойца» и выпустить токаря. Или другой — с целью подготовки фрезеровщика. Ну а абстрактная функция — это та самая функция, которой класс не учит. Но он может уже объяснить специалисту что делать после того, как он научится этой функции — скажем не обучив его обработке деталей он может объяснить как обработать ящик деталей: взять из входного лотка первую деталь, обработать её (этому вы научитесь позже), положить в лоток обработанную деталь, повкторить до исчерпания входного лотка… Все предупреждения про переучивание специалистов остаются в силе: если классы будут плохо согласованы между собой… ну вы же учились в школе, правда?

Ещё хотите. Ну давайте. Про виртуальные функции. Что такое — виртуальные функции? Ну это просто функции объектов. На самом-то деле виртуальные функции никакими особенностями не обладают. Особенностями обладают НЕвиртуальные функции. Это — попытка «всё ускорить и улучшить», приводящая к проблемам. Действительно: человечки выглядят хорошо на бумаге, но программа-то выполняется на компьютере! А он довольно туп. Выполняет с десяток элементарных операций (сложение, вычитание, etc), а всему остальному его нужно учить. Что это означает для нашего подхода? Это значит что если нам нужно попросить «специалиста по работе на станкам» выключить станок, то компьютеру нужно «посмотреть ему в голову», понять как он выключает станки и, собственно, это сделать. А если операция выключения станка очень проста? Мы больше времени потратим на поиск данных в голове у специалиста! И зачастую операция выключения станка для всех станков одинакова. Крайняя кнопка слева. Возникает «гениальная» идея: давайте увидев что нам нужно выключить станок, на котором работал специалист наживать крайнюю кнопку слева. Так работает невиртаульная функция: она смотрит на резюме человека — и решает что нужно делать в том или ином случае. Всё будет работать. Более того, если появится хитрый сверлильный станок с кнопкой выключения справа, то всё будет нормально если мастер, управляющий цехом, будет знать что это она имеет дело со сверлильным станком и специалистом, работающим на нём. Но вот если команда выключить станки поступит от пожарной инспекции, которая не знает о таких тонкостях… она попросит всех специалистов выключить станки, они все нажмут крайнюю левую кнопку (помните — мы решили «не смотреть специалисту в голову» для ускорения работы программы), в том числе так сделает человек, управляющий сверлильным станком… треск, шум, гам, «программа выполнила недопустимую операцию и будет закрыта»…

Ну а невиртуальный деструктор — это просто невиртуальная функция, которая увольняет нашего специалиста. Понятно что функции, которая так сильно влияет на специалиста уж сам бог велел быть виртуальной, но… что если у него других невиртуальных функций нет? Это ж нужно будет «в голове у специалиста» организовать целый раздел со списком функций и его обработкой — и всё ради одной функции! Иногда это кажется перебором и если у объекта нет никаких виртуальных функций то и его деструктор делают невиртуальным. Обычно переучивать таких специалистов — себе дороже…

Вот примерно и всё. А как эти принципы использовать на практике и в том или ином языке программирования — это уже другой вопрос. В программах на PHP очень часть бывают полиморфными классы работы с базами данных: у разных баз данных свои заморочки (скажем чтобы положить в базу "Hello, world!" в кавычках нужно иногда использовать insert ("""Hello, World!"""), а иногда insert("\"Hello, World\""), но 90% функциональности ведь совпадают!

P.S. Для тех кто хочет посмеяться над проблемами, порождаемыми наследованием подборка приколов (наводка тов. Stepanow). Обратите внимание на то, что почти все проблемы вызваны «тараканами в голове» от «неполного переучивания» при наследовании…
Tags:
Hubs:
Total votes 121: ↑85.5 and ↓35.5+50
Comments122

Articles