понедельник, 29 февраля 2016 г.

Паттерн "Стратегия"

Сегодня, копаясь в архивах, случайно нашел замечательную книгу Eric Freeman "Head First Design Patterns".
Ощущения чем-то сродни тем, которые испытываешь от найденной на дачном чердаке книги во время уборки в жаркий летний день. С неожиданной находки сметаешь пыль и усевшись на каком-то старом хламе, с удовольствием начинаешь ее листать.
Быстро пробежал глазами первый паттерн "Стратегия", еще раз восхитившись тем, как толково и доступно все написано. Почему-то зацепила фраза о том, что для понимания сути примеров нужно как минимум знать Java или хотя бы С#. Ой, да ладно. А почему не C++ или Python? Я имею право на такую постановку вопроса, так как сейчас работаю в проекте, написанном на Java. Добрую часть карьеры я был иногда слишком "религиозен" насчет операционных систем и языков программирования, но затем понял, что не обязан приносить клятву верности только избранной технологии или языку и считать их лучшими, отметая остальные. Именно поэтому вместо популярных ныне Java и С# я в качестве развлечения реализовал описанный пример на C++:
#include <iostream>

namespace hfstrategy {
    class FlyBehavior {
        public:
            virtual void fly() = 0;
    };

    class FlyWithWings: public FlyBehavior {
        public:
            void fly() { std::cout << "I'm flying!!" << std::endl; }
    };

    class FlyRocketPowered: public FlyBehavior  {
        public:
            void fly() { std::cout << "I'm flying with a rocket." << std::endl; }
    };

    class FlyNoWay: public FlyBehavior  {
        public:
            void fly() { std::cout << "I can't fly." << std::endl; }
    };

    class QuackBehavior {
        public:
            virtual void quack() = 0;
    };

    class FakeQuack: public QuackBehavior {
        public:
            void quack() { std::cout << "Qwak" << std::endl; }
    };

    class MuteQuack: public QuackBehavior {
        public:
            void quack() { std::cout << "<< Silence >>" << std::endl; }
    };

    class Squeak: public QuackBehavior {
        public:
            void quack() { std::cout << "Squeak" << std::endl; }
    };

    class Quack: public QuackBehavior {
        public:
            void quack() { std::cout << "Quack" << std::endl; }
    };

    class Duck {
        FlyBehavior *flyBehavior;
        QuackBehavior *quackBehavior;
        public:
            Duck() { }
            void setFlyBehavior (FlyBehavior *fb) { flyBehavior = fb; }
            void setQuackBehavior(QuackBehavior *qb) { quackBehavior = qb; }
            virtual void display() = 0;
            void performFly() { flyBehavior->fly(); }
            void performQuack() { quackBehavior->quack(); }
            void swim() { std::cout << "All ducks float, even decoys!" << std::endl; }
    };

    class RubberDuck: public Duck {
        public:
            RubberDuck() {
                FlyNoWay objf;
                Squeak objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a rubber duckie" << std::endl; }
     };

    class MallardDuck: public Duck {
        public:
            MallardDuck() {
                FlyWithWings objf;
                Quack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a real Mallard duck" << std::endl; }
     };

    class ModelDuck: public Duck {
        public:
            ModelDuck() {
                FlyNoWay objf;
                Quack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a model duck" << std::endl; }
     };

    class RedHeadDuck: public Duck {
        public:
            RedHeadDuck() {
                FlyWithWings objf;
                Quack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a real Red Headed duck" << std::endl; }
     };

    class DecoyDuck: public Duck {
        public:
            DecoyDuck() {
                FlyNoWay objf;
                MuteQuack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a duck Decoy" << std::endl; }
     };
}

int main() {
    using namespace hfstrategy;
    Duck *ptrd;
    RubberDuck rubberduck;
    MallardDuck mallardduck;
    ModelDuck modelduck;
    RedHeadDuck redheadduck;
    DecoyDuck decoyduck;

    ptrd = &decoyduck;
    ptrd->display();
    ptrd->performFly();
    ptrd->performQuack();
    ptrd->swim();
    return 0;
}


$ g++ -g -Wall -std=c++11 duck.cpp -o duck
$ ./duck.exe
I'm a duck Decoy
I can't fly.
<< Silence >>
All ducks float, even decoys!
Вот уж воистину точно сказано было: "Когда коту программисту делать нечего, он удовольствия ради код с Жабы на Цэ Два Креста перепихивать станет!" :)
И что с этим делать? Удалить это все и забыть книгу до следующей "уборки на чердаке"? Решил оставить. Кто знает, может это все кому-нибудь пригодится, а если вдруг пригодится, то не забудьте слова "Банды четырех":
Зависимость от реализации может повлечь за собой проблемы при попытке повторного использования подкласса. Если хотя бы один аспект унаследованной реализации непригоден для новой предметной области, то приходится переписывать родительский класс или заменять его чем-то более подходящим. Такая зависимость ограничивает гибкость и возможности повторного использования. С проблемой можно справиться, если наследовать только абстрактным классам, поскольку в них обычно совсем нет реализации или она минимальна.
Продолжение Паттерн "Стратегия": жаркое из питона.

понедельник, 15 февраля 2016 г.

Мягкий путь программиста

И снова о книгах.
«Я называю трансцендентальным всякое познание, занимающееся не столько предметами, сколько видами нашего познания предметов, поскольку это познание должно быть возможным a priori» (Кант И., Соч., т. 3, М., 1964, с. 121)
Продолжая и дальше почти "по-чапаевски" путать Канта с Шопенгауэром, начал я читать довольно свеженькую книгу с интригующим философским названием "Путь программиста", ну прям-таки "Дао дэ дзин" для софтмэйкеров, хотя в оригинале название звучит весьма прозаично "Soft Skills The software developer's life manual". Автор Сонмез Джон.
Прочитал первые 12 глав. Главы небольшие, книга читается легко, написана неплохо и не нагружает слишком уж безапелляционными высказываниями, хотя некоторые мысли своей очевидностью вызывают желание пролистать пару страниц.
Привычка читать книги и выделять маркером интересные и/или сомнительно-занятные мысли позволяет в последствии освежить основные положения книги, не читая ее повторно полностью. Кое-что, помеченное желтым маркером, я привожу ниже.
  • Вы можете наладить все остальные области жизни, но, если не сумеете преодолеть прокрастинацию, неорганизованность и банальную лень, вам будет очень трудно выйти даже на вторую передачу.
  • Ваше отношение к тому, что вы можете предложить в качестве бизнеса, значительно влияет на то, как вы видите свою карьеру. Компании часто пересматривают свои продукты и улучшают их. Вам следует поступать так же. Услуги, которые вы предоставляете в качестве разработчика ПО, могут изменить свою ценность, и ваша задача заключается не только в том, чтобы оценить их стоимость, но и в том, чтобы определить, чем они отличаются от предложений тысяч других разработчиков ПО.
  • Cфокусируйтесь на том, чтобы стать специалистом, предоставляющим специализированные услуги определенной части клиентов.
  • Если бы вам пришлось описать одним предложением определенную услугу, которую вы можете предоставить перспективному работодателю или покупателю, как бы звучало это предложение?
  • Предоставленные сами себе, мы следуем по проторенной дорожке. Выбрать свой путь гораздо сложнее. Вместо этого беремся за первую попавшуюся работу и остаемся на этом месте до тех пор, пока не появляется место получше или нас не увольняют.
  • По какой бы причине вы ни отказывались определять свои цели, сейчас пришло время сделать это. Не завтра, не на следующей неделе, а именно сейчас. Каждый шаг, сделанный без понимания направления, сделан впустую. Вам не следует идти по жизни, не определив цели своей карьеры.
  • ...награждение за хорошее поведение мотивирует гораздо лучше, чем наказание за плохое.
  • Если хотите вдохновлять, хвалите, вместо того чтобы критиковать.
  • Я считаю разработчика предпринимателем, если он развивает свой бизнес или продукт с использованием профессиональных навыков. В то время как служащий и независимый консультант меняют свое время на деньги, предприниматель меняет свое время не на моментальную выручку, а на шанс получить высокую оплату в будущем. …Я все еще пишу код, но обычно не для определенного клиента. Я пишу код либо своего продукта, который создаю, либо для обучающих материалов.
  • Множество разработчиков не имеют определенной специализации. Большинство определяют свою специализацию исключительно языком программирования, с которым они работают. Вы, скорее всего, слышали: «Я пишу на C#» или «Я разработчик на Java». Подобная специализация слишком широкая. Она никак не описывает, какую работу вы можете выполнять. Язык программирования не поясняет мне ничего о том, каким разработчиком вы являетесь и что можете делать. Это говорит только об одном инструменте, который вы можете использовать для решения задач. … Если вы специалист, у вас будет немного потенциальных работодателей и клиентов, но вы станете гораздо привлекательнее для них как профессионал. Пока вашу специализацию не выбрало большое количество программистов, будет гораздо проще найти работу или клиента, чем если бы вы просто назвали себя разработчиком.
  • Правило специализации гласит следующее: чем уже ваша специализация, тем меньше потенциальных рабочих мест и тем больше шанс, что вы получите одно из них.
  • Иметь в команде разработчика, который может выполнять любую работу, довольно удобно, но компании и клиенты редко ищут именно такого человека. Даже несмотря на то, что вы отлично знаете все виды технологий и 50 языков программирования, вам все равно следует выбрать специальность, даже если она будет меняться время от времени.
  • Вам следует учиться не только разработке ПО. Уделите время вопросам лидерства, менеджмента и бизнеса, если планируете в будущем занять управляющие позиции.
  • Вам все еще нужно знать, сколько нужно брать с клиентов, чтобы обеспечить себя, но вам придется достичь точки, когда сможете обосновать заданную (или даже более высокую) цену на рынке. Это нужно делать, основываясь не на ставке, а на том, сколько ваша работа стоит для клиентов. Вы можете рассматривать свою деятельность как товар или как услугу, которая повышает прибыльность ваших заказчиков. Если вы решаете рассматривать свое занятие как товар, то можете посоревноваться за работу с другими разработчиками (притязания которых часто ниже ваших). В этом случае покупатель выберет фрилансера с самой низкой ставкой. Но, если вы продвигаете свои услуги, основываясь на том, сколько сможете сэкономить клиенту или как способны расширить его бизнес, можно рассчитать свою ставку исходя из того, какую пользу принесут клиенту ваши услуги. Именно поэтому важно иметь специализацию.
  • Помните, что нужно рассказать о пользе, которую вы можете принести, а не о том, что вы можете сделать с технической точки зрения.
  • Многие разработчики, только недавно ставшие предпринимателями, допускают распространенную ошибку — создают продукт до того, как найдут целевую аудиторию. Хотя может показаться логичным начать с создания продукта, вам следует избегать этой оплошности.
  • Если вы хотите создать продукт, первым делом нужно определить целевую аудиторию, которой может понадобиться ваше решение. У вас может быть общее представление о том, какую проблему вы можете решить для этой аудитории, но в большинстве случаев потребуется провести исследования, чтобы найти распространенную проблему, которая еще не решена или решена, но некачественно
  • Вместо того чтобы пытаться найти аудиторию, вы можете ее создать.
Ну, пока на этом и все.

Продолжение следует.

пятница, 12 февраля 2016 г.

"Чапаев и Пустота" или как перепутать Канта с Шопенгауэром.

В очередной раз с удовольствием перечитал Виктора Пелевина. Наиболее понравившиеся фрагменты я сохранил и решил опубликовать. Я знаю о существовании Материала из Викицитатника. Однако мне ближе именно те моменты, на которые я сам обратил внимание в процессе чтения, и они вовсе необязательно пересекаются с Викицитатником. Именно ими я и решил поделиться здесь.
...если пытаешься убежать от других, то поневоле всю жизнь идешь по их зыбким путям. Уже хотя бы потому, что продолжаешь от них убегать. Для бегства нужно твердо знать не то, куда бежишь, а откуда. Поэтому необходимо постоянно иметь перед глазами свою тюрьму.
Мир, в котором мы живем - просто коллективная визуализация, делать которую нас обучают с рождения. Собственно говоря, это то единственное, что одно поколение передает другому. Когда достаточное количество людей видит эту степь, траву и летний вечер, у нас появляется возможность видеть все это вместе с ними. Но какие бы формы не были нам предписаны прошлым, на самом деле каждый из нас все равно видит в жизни только отражение своего собственного духа. И если вы обнаруживаете вокруг себя непроглядную темноту, то это значит только, что ваше собственное внутреннее пространство подобно ночи. Еще хорошо, что вы агностик. А то знаете, сколько в этой темноте шастало бы всяких богов и чертей.
У нас внутри - весь кайф в мире. Когда ты что-нибудь глотаешь или колешь, ты просто высвобождаешь какую-то его часть. В наркотике-то кайфа нет, это же просто порошок или вот грибочки... Это как ключик от сейфа. Понимаешь?
Лес вокруг дрожал ровными радужными огнями непонятной природы, а в небе над поляной вспыхивали удивительной красоты мозаики, не похожие ни на что из того, что встречает человек в своей изнурительной повседневности. Мир вокруг изменился - он сделался гораздо более осмысленным и одушевленным, словно бы стало наконец понятно, зачем на поляне растет трава, зачем дует ветер и горят звезды в небе.
-Ты, Коль, сам подумай - у нас же страна зоной отродясь была, зоной и будет. Поэтому и Бог такой, с мигалками. Кто тут в другого поверит?
- А может, все и не так на самом деле. Может, не потому Бог у нас вроде пахана с мигалками, что мы на зоне живем, а наоборот - потому на зоне живем, что Бога себе выбрали вроде кума с сиреной.
- В лес бежим, быстро! - повторил Володин и вскочил на ноги.
- Какой лес? Ведь никакого леса нет на самом деле!
- Ты, главное, беги, а лес образуется. Давай беги!
- Простите, не понял, - сказал я, - что, мое воспаленное сознание порождает кошмар или само сознание является порождением кошмара?
- Это одно и то же, - махнул рукой Чапаев. - Все эти построения нужны только для того, чтобы избавиться от них навсегда. Где бы ты ни оказался, живи по законам того мира, в который ты попал, и используй сами эти законы, чтобы освободиться от них.
- А, - улыбнулся Чапаев, - вот вы о чем. Знаете, Петр, когда приходится говорить с массой, совершенно не важно, понимаешь ли сам произносимые слова. Важно, чтобы их понимали другие. Нужно просто отразить ожидания толпы. Некоторые достигают этого, изучая язык, на котором говорит масса, а я предпочитаю действовать напрямую.
...В терминологии Чапаева это означало изучить язык, на котором говорит масса. А сам он, как я понял, даже не утруждал себя знанием слов, которые произносил. Было, правда, неясно, как он этого достигает. Может быть, впадая в подобие транса, он улавливал эманации чужого ожидания и каким-то образом сплетал из них понятный толпе узор.
Мне не понравился их комиссар, - сказал я, - этот Фурманов. В будущем мы можем не сработаться.
- Не забивайте себе голову тем, что не имеет отношения к настоящему, - сказал Чапаев. - В будущее, о котором вы говорите, надо еще суметь попасть. Быть может, вы попадете в такое будущее, где никакого Фурманова не будет. А может быть, вы попадете в такое будущее, где не будет вас.
- ... человек чем-то похож на этот поезд. Он точно так же обречен вечно тащить за собой из прошлого цепь темных, страшных, неизвестно от кого доставшихся в наследство вагонов. А бессмысленный грохот этой случайной сцепки надежд, мнений и страхов он называет своей жизнью. И нет никакого способа избегнуть этой судьбы.
- Ну отчего, - сказал Чапаев. - Способ есть.
- И вы его знаете? - спросил я.
- Конечно, - сказал Чапаев.
Поразительно, сколько нового сразу же открывается человеку, стоит только на секунду опустошить заполненное окаменелым хламом сознание! Неясно даже, откуда приходит большая часть звуков, которые мы слышим. Что же тогда говорить обо всем остальном, какой смысл пытаться найти объяснения нашей судьбе и нашим поступкам, основываясь на том немногом, что, как нам кажется, мы знаем!
- Я, Василий Иванович, думаю о том, что любовь прекрасной женщины - это на самом деле всегда снисхождение. Потому что быть достойным такой любви просто нельзя.
...
- Серьезно? - спросил Чапаев. - Ну ладно. Тогда гляди - снисхождение всегда бывает от чего-то одного к чему-то другому. Вот как в этот овражек. От чего к чему это твое снисхождение сходит?
Я задумался. Было понятно, куда он клонит. Скажи я, что говорю о снисхождении красоты к безобразному и страдающему, он сразу задал бы мне вопрос о том, осознает ли себя красота и может ли она оставаться красотой, осознав себя в этом качестве. На этот вопрос, доводивший меня почти до безумия долгими петербургскими ночами, ответа я не знал. А если бы в виду имелась красота, не осознающая себя, то о каком снисхождении могла идти речь? Чапаев был определенно не прост.
Чапаев поглядел вверх и покачнулся.
- Красота? - переспросил он задумчиво. - А что такое красота?
- Ну как, - сказал я. - Как что. Красота - это совершеннейшая объективация воли на высшей ступени ее познаваемости.
Чапаев еще несколько секунд глядел в небо, а потом перевел взгляд на большую лужу прямо у наших ног и выплюнул в нее окурок. Во вселенной, отраженной в ровной поверхности воды, произошла настоящая катастрофа: все созвездия содрогнулись и на миг превратились в размытое мерцание.
- Что меня всегда поражало, - сказал он, - так это звездное небо под ногами и Иммануил Кант внутри нас.
- Я, Василий Иванович, совершенно не понимаю, как это человеку, который путает Канта с Шопенгауэром, доверили командовать дивизией.
- Все, что мы видим, находится в нашем сознании, Петька. Поэтому сказать, что наше сознание находится где-то, нельзя. Мы находимся нигде просто потому, что нет такого места, про которое можно было бы сказать, то мы в нем находимся. Вот поэтому мы нигде. Вспомнил?
- В древние времена, - сказал Кавабата, - в нашей стране чиновников назначали на важные посты после экзаменов, на которых они писали сочинения о прекрасном. И это был очень мудрый принцип - ведь если человек понимает в том, что неизмеримо выше всех этих бюрократических манипуляций, то уж с ними-то он без сомнения справится.
- Это тоже один из принципов нашей фирмы. Мы всегда стараемся проникнуть глубоко в душу того народа, с которым ведем дела. Дело здесь не в том, что мы хотим извлечь благодаря этому какую-то дополнительную прибыль, поняв... Как это по-русски? Ментальность, да?
Сердюк кивнул.
- Нет, - продолжал Кавабата. - Дело здесь скорее в желании возвысить до искусства даже самую далекую от него деятельность. Понимаете ли, если вы продаете партию пулеметов, так сказать, в пустоту, из которой вам на счет поступают неизвестно как заработанные деньги, то вы мало чем отличаетесь от кассового аппарата. Но если вы продаете ту же партию пулеметов людям, про которых вам известно, что каждый раз, когда они убивают других, они должны каяться перед тремя ипостасями создателя этого мира, то простой акт продажи возвышается до искусства и приобретает совсем другое качество. Не для них, конечно, - для вас. Вы в гармонии, вы в единстве со вселенной, в которой вы действуете, и ваша подпись под контрактом приобретает такой же экзистенциальный статус... Я правильно говорю это по-русски?
- А вот скажите, - заговорил он, - чего хочет человек, вернувшийся домой из опасного путешествия, после того как утолит жажду и голод?
- Не знаю, - сказал Сердюк. - У нас обычно телевизор включают.
- Не-е-е, - сказал Кавабата. - Мы в Японии производим лучшие телевизоры в мире, но это не мешает нам осознавать, что телевизор - это просто маленькое прозрачное окошко в трубе духовного мусоропровода. Я не имел в виду тех несчастных, которые всю жизнь загипнотизированно смотрят на бесконечный поток помоев, ощущая себя живыми только тогда, когда узнают банку от знакомых консервов.
I’m not a transcendentalist monk sitting on the floor meditating while eating Peyote buttons, trying to help you ascend to a higher state of consciousness, но мне очень нравится это замечательное произведение. В который раз его перечитываю, а свежесть мыслей и восприятия остаются прежними.
Еще в школьные годы довелось прочитать другого Чапаева, который без Пустоты... Это был Чапаев Д.Фурманова. Почему-то из всего произведения запомнилась только одна специфическая особенность: Фурманов зачем-то в тексте все время "штокал", и это ужасно раздражало.
- Научатся, браток, научатся... На фронт приедут - там живо сенькину мать куснут...
- А што думал - на фронте тебе не в лукошке кататься...
И все заерзали, засмеялись, шеями потянулись вперед.
- Вон Терентия не узнаешь, - в заварке-то мазаный был, как фитиль, а тут поди тебе... Козырь-мозырь...
- Фертом ходит, што говорить... Сабля-то - словно генеральская, ишь таскается.
Два совершенно разных Чапаева. Што и говорить, дуализЬм эдакий, понимаешь.

Думал над Гурджиевскими словами "Человек не имеет индивидуальности; у него нет единого большого Я. Человек расщеплен на множество мелких "я"...
Интересная получается картина, если эти слова попробовать анализировать с точки зрения официальной медицины с целью постановки диагноза, который по сути своей с позиций теории познания был бы субъективным отражением объективно существующей истины. Предприняв попытку познания этой самой объективной истины, можно прийти к интересным выводам. Понятие "диагноз", употребляемое в данном контексте, требует уточнения. Здесь речь идет о «диагнозе болезни», который медики называют еще «нозологическим диагнозом», а не о «диагнозе больного», то есть «индивидуальном диагнозе» или «патогенетическом диагнозе» (diagnosis aegroti).
Похоже на то, что Гурджиев описывает явную диссоциативную патологию личностной идентичности, вследствие которой личность индивида разделяется, что формирует впечатление, будто бы в одном человеке, а точнее человеческом субъекте, уживаются две и более личностей, то самое "множество мелких "я", имеющих право "подписывать чеки и векселя". Это множество мелких "я", по всей видимости, есть не что иное как эго-состояния.
"И каждое отдельное малое "я" может называть себя именем целого, действовать во имя целого, соглашаться или не соглашаться, давать обещания, принимать решения, с которыми придется иметь дело другому "я" или всему целому." Налицо "переключение", вследствие которого в индивиде одна личность ("мелкое я" по Гурджиеву) становится на замену другой. Находясь в различных эго-состояниях, индивид реагирует на одинаковые повседневные ситуации по-разному вследствие того, что каждое его эго имет свой собственный набор паттернов восприятия окружающего мира. Малое "я", захватившее в данный момент рычаги управления, после очередного "переключения" может и не помнить того, что происходило в периоды правления другого малого "я", и "может в какой-то момент; что-то пообещать уже не себе, а кому-то другому, просто из тщеславия или для развлечения. Затем это "я" исчезает; но человек, т.е. сочетание других "я", совершенно не ответственных за это обещание, вынужден расплачиваться за него в течение всей своей жизни." Здесь появляются довольно веские основания для констатации факта болезни раздвоения личности. Следовательно, причину существования гурджиевских "малых "я" можно рассматривать под углом зрения причин синдрома раздвоения личности.
Запуск того или иного "малого я", то есть переход индивидуума в одно из возможных "малых я" является функцией двух переменных - внутреннего состояния и входного воздействия (пускового триггера). Роль таких триггеров могут играть, например, травмирующие воздействия - стрессы. В любом случае переход от одного малого "я" к другому выглядит как защитная реакция психики, основывающаяся на полученном ранее травматичесом опыте.
Характерным признаком раздвоения идентичности является частичная невозможность доступа к информации, приобретенной одной личностью, во время правления другой. В случае гурджиевских "малых я" все личности имеют доступ практически ко всей информации, которая в этом случае является коллективным приобретением, что при значительном количестве, а главное - уровне самоуправства каждого "малого я" может основательно осложнить жизнь индивидуума, того "большого Я", которого может и не оказаться.
И если ты долго смотришь в Пустоту, то Чапаев будет вглядываться в тебя. Как это будет, ежели обратно на немецкий перепихнуть? Э-э[1]... Und wenn du lange in eine Leere blickst, blickt Tschapajew auch in dich hinein. Брр!.. Ницшеанство какое-то... :)

Примечания
1. "Сражающемуся с чудовищами следует позаботиться о том, чтобы самому не превратиться в чудовище. И если ты долго смотришь в бездну, то бездна тоже смотрит в тебя." (Фридрих Ницше)

пятница, 5 февраля 2016 г.

Как кратко и доступно объяснить суть динамического полиморфизма?

Недавно на посиделках в кафе в ходе обсуждения какой-то технической темы промелькнули слова "динамический полиморфизм", которые вызвали неожиданный интерес у одного из собеседников, ранее писавшего на скриптовом языке и подозревающего о том, что где-то есть какие-то загадочные указатели... Возник вопрос, формулировка которого вынесена в заголовок этого поста.
Вопрос далеко не оригинальный и ответ на него давно известен. И тем не менее, ну как быстро, кратко и доступно объяснить суть динамического полиморфизма, не залезая в дебри и не загружая вопрошающего кучей подробностей? Отмахнуться от собеседника, предложив спросить у Google, или все-таки попробовать объяснить? Объяснение было мною дано и на какой-то старой квитанции написан код, а потом возникла идея: сохранить это объяснение здесь, в когда-то давно начатом и потом забытом блоге "Записи на манжетах." Откуда взялось такое название? Все просто. И.Ильф и Е.Петров "Золотой теленок":

- Вот как! - сказал Остап. - А себя вы считаете, очевидно, врачом-общественником? Джентльменом? Тогда вот что: если вам, как истому джентльмену, взбредет на мысль делать записи на манжетах, вам придется - писать мелом.
- Почему? - раздраженно спросил новый пассажир.
- Потому что они у вас совершенно черные. Не от грязи ли?
- Вы жалкий, ничтожный человек! - быстро заявил Паниковский.
- И это вы говорите мне, своему спасителю? - кротко спросил Остап, - Адам Казимирович, остановите на минутку вашу машину. Благодарю вас. Шура, голубчик, восстановите, пожалуйста, статус-кво.


Итак, существует термин "раннее связывание", касающийся настройки вызовов обычных функций, при компиляции которых известна вся необходимая для их вызова адресная информация. Настройка вызовов может происходить не только при компиляции, но и в процессе выполнения программы - это, так называемое, позднее связывание. Вызов функции позднего связывания - это вызов, при котором адрес вызываемой функции до запуска программы неизвестен и определяется в процессе выполнения программы.
В C++ функцией позднего связывания является виртуальная функция, которая может вызываться так же, как и любая другая функция-член.
Динамический полиморфизм поддерживается за счет использования вызова виртуальной функции через указатель базового класса. Указатель, объявленный в качестве указателя на базовый класс, также может использоваться, как указатель на любой класс, производный от этого базового. В этом случае в процессе работы программа обязана определить, на какой тип объекта ссылается этот указатель, а затем выбрать, какую версию подменяемой функции выполнить. Определение конкретной версии виртуальной функции имеет место не в процессе компиляции, а уже в процессе выполнения программы.
Есть некий базовый класс, содержащий виртуальную функцию, и различные производные от него классы. Если указатель базового класса ссылается на разные объекты этих производных классов, то выполняются различные версии виртуальной функции. Этот процесс по сути своей и является реализацией принципа динамического полиморфизма.

Пример.
Есть полиморфный базовый класс Base с чистой виртуальной функцией func(). Этот класс наследуется двумя производными классами Derived1 и Derived2, по-своему переопределяющими виртуальную функцию базового класса. Выбор конкретной версии функции func() происходит непосредственно во время работы программы. При компиляции этот выбор сделать невозможно, поскольку он основан на значениях, определяемых вызовом функции rand(), и которые можно получить только во время работы программы. Если в ходе работы генерируется нечетное число, то используется объект obj1. В противном случае - объект obj2.
#include <iostream>
#include <cstdlib>
using namespace std;

class Base {
public:
    int i;
    Base(int x) { i = x; }
    virtual void func() = 0;
};

class Derived1: public Base {
public:
    Derived1(int x) : Base(x) { }
    void func() { cout << "Call func() in Derived1: " << i+i << endl; }
};

class Derived2: public Base {
public:
    Derived2(int x) : Base(x) { }
    void func() { cout << "Call func() in Derived2: " << i*i << endl; }
};

int main() {
    Base *p; 
    Derived1 obj1(5); 
    Derived2 obj2(5); 

    for (int i=0; i<5; i++) {
        if (rand() % 2) {
            p = &obj1;
        } else {
            p = &obj2;
        }
        p->func();
    }
    return 0;
}
Примерно так все и выглядело. Разговор потом развивался дальше в сторону множественного наследования, но к этому моменту тот, кто задал изначальный вопрос, уже ушел. Хочется верить, что он все или почти все понял. Именно по его просьбе и для него это все было рассказано и потом записано. :)
Если на очередных посиделках опять будут подобные вопросы, то ответы на них я тоже постараюсь опубликовать, дабы поддержать эту дебютную идею.

Спустя месяц после опубликования поста, эту тему опять вспомнили. Решено было прямо здесь ее продолжить. Была высказана просьба - "намекнуть на типичные грабли, которые могут случаться" в смысле "what can possibly go wrong".
Просьбу удовлетворяю и намекаю:
#include <iostream>
class Base {
public:
   virtual void func(short) {std::cout << "Base::func" << std::endl;}
   virtual void func1(short) const {std::cout << "Base::func1" << std::endl;}
};

class Derived: public Base {
public:
   // Это перегруженные методы!
   virtual void func(int) {std::cout << "Derived::func" << std::endl;}
   virtual void func1(short) {std::cout << "Derived::func1" << std::endl;}
};

int main() {
    Base base;
    Derived obj; 
    Base *p;        
        
    p = &base;
    p->func(5);
    p->func1(5);
        
    p = &obj;
    p->func(5);
    p->func1(5);

    return 0;
}

$ g++ -Wall dinp.cpp -o dinp
$ ./dinp.exe
Base::func
Base::func1
Base::func
Base::func1
И чтобы блюдо получилось, чуточку приправим все щепоткой Страуструпа, пример из которого я слегка поправил для большей наглядности:
#include <iostream>

class Base {
public:
    virtual void func() const { std::cout << "Base::func() " << std::ends; }
    void g() const { std::cout << "Base::g()" << std::endl; }
};

class Derived: public Base {
public:
    void func() const { std::cout << "Derived::func() " << std::ends; } // Не замещает Base::func()
    void g() const { std::cout << "Derived::g()" << std::endl; }
};

class Derived2: public Derived {
public:
    void func() { std::cout << "Derived2::func() " << std::ends; }  // Не замещает Derived::func()
    void g() const { std::cout << "Derived2::g()" << std::endl; }
};

void call(const Base& b) {
    b.func();
    b.g();
}

int main() {
    Base b;
    Derived d;
    Derived2 d2;

    call(b);   // Base::func() Base::g()
    call(d);   // Derived::func() Base::g()
    call(d2);  // Derived::func() Base::g()

    b.func();  // Base::func().
    b.g();     // Base::g()

    d.func();  // Derived::func()
    d.g();     // Derived::g()

    d2.func(); // Derived2::func()
    d2.g();    // Derived2::g()

    return 0;
}
Тут есть еще о чем поговорить, плавно съехав в C++ версии 11, но это будет явный перебор. На этом закругляемся. (It took me quite some time to become aware of an amazing analogy that exists between the culinary art and the art of computer programming.)