SoftCraft
разноликое программирование

Top.Mail.Ru

Хаос, эволюция, программирование

Теория хаоса — это раздел математики и физики, изучающий поведение нелинейных динамических систем, крайне чувствительных к начальным условиям. Малые изменения (например, взмах крыла бабочки) могут привести к непредсказуемым глобальным последствиям («эффект бабочки»). Хаос — это не беспорядок, а сложная, детерминированная, но непредсказуемая долгосрочно система.

Отрывки из Википедии. Теория хаоса

© 2025
Легалов А.И.

Примеры всех рассмотренных программ (123.7 кб)

Эффект бабочки

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

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

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

Эволюция животного мира и хаос

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

Развивая этот материал, в рамках методических наработок я записал свою версию Животного мира в котором рассмотрел различные альтернативные решения для трех рассматриваемых парадигм. В качестве бонуса также показал возможности процедурно-параметрической парадигмы программирования (4П) по расширению уже написанного кода в рамках некоторого хаотического развития. В представленных ниже рассуждениях я по сути повторяю многое из того, что есть в этом видео.

Код, используемый в этом видео, лежит в одном из каталогов репозитория с примерами использования процедурно-параметрического программирования (ППП). Он также размещен в подкаталоге "all/animals" прилагаемого архива. Вместе с тем я продолжил также развлечение, связанное с расширением уже существующих проектов путем использования в следующих расширениях единиц компиляции из ранее написанных проектов, чтобы посмотреть, сколько изменений придется внести в случае хаотического расширения. Этот код, затрагивающий только ПП парадигму, также сопровождает данный текст (находясь в подкаталоге "all/animals-reuse" архива) и размещен на гитхабе в соответствующем каталоге.

ООП, разбавленный интерфейсами

Для того, чтобы сопоставить различие в восприятии процессов разработки с использованием различных парадигм, начнем с ОО подхода. Изначально мы знаем, что хотим сформировать некоторый "зоопарк", поэтому понятие о животных, отображаемое в соответствующем интерфейсе AnyAnimal (каталог архива "all/animals/01-start/oop-interface/"), задающем общие характеристики различных живых существ. Конкретно поведение будет уточняться в специализациях описывающих животных в производных классах. В качестве базовых свойств, опираясь на окончательное решение, приведенное в упомянутых выше "Больших проблемах наследования в ООП", выделим только перемещение, определив также для него некоторое телодвижение по умолчанию. Использование структур вместо классов обусловлено только стремлением сократить код, убрав лишние ключевые слова.


  // Интерфейсный класс, обобщающий всех конкретных животных.
  // Включает только общие для всех методы. Их состав может изменяться
  // Допускает использование методов по умолчанию.
  struct AnyAnimal {
    // Все животные могут иметь общие признаки, но с разными способами реализации
    // Например, перемещаться. Даже если мы не знаем как. Обобщим перемещение.
    // Для этого используем виртуальный обработчик по умолчанию
    virtual void Moving();
  };

  // Перемещение по умолчанию для всех животных, в которых оно не переопределено.
  void AnyAnimal::Moving() {
    printf("Я нечто. Как-то двигаюсь, но никто не знает как\n");
  }

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


  // Интерфейсный класс для плавающих животных
  // Имеет чистый метод. Значит нужно по разному определять для разных животных
  struct SwimmingAnimal {
    // Для этой группы своя обобщающая функция. Пусть будет абстрактной
    virtual void Swimming() = 0;
  };

  // Интерфейсный класс для животных, которые издают звуки.
  struct SpeakingAnimal {
    // Для этой группы своя обобщающая функция. Обработчик по умолчанию
    virtual void Speaking();
  };

  // Голос по умолчанию, если метод не переопределен
  void SpeakingAnimal::Speaking() {
    // Обобщающая функция задает звучание по умолчанию, когда не знаем способ
    printf("Я нечто. Но я пою как никто. Кто я?\n");
  }

Таким образом, поведение животных выходит в ООП на первый план, предшествуя их появлению в программе. Разнообразие, связанное с сотворением мира тварей (по паре или без), становится вторичным. и опирается на те, свойства, которые задуманы и описаны выше. В данном случае "создатель" ограничился сотворением слона, собаки, пингвина и червя, включив соответствующие интерфейсы в их классы и описав поведение следующим образом.


  //------------------------------------------------------------------------------
  // Это слон
  struct Elephant: AnyAnimal, SpeakingAnimal {
    // Он топает при ходьбе, что описано в реализации
    virtual void Moving();
    // Он может издавать уникальные звуки, что описано в реализации
    virtual void Speaking();
  };

  // Слон может издавать звуки
  void Elephant::Speaking() {
    printf("Я слон. Я иду и пою: ");
    printf("Ду-ду\n");
  }
  // Он топает при ходьбе
  void Elephant::Moving() {
    printf("Я есть слон. Я топаю: ");
    printf("Топ-топ\n");
  }

  //------------------------------------------------------------------------------
  // Это собака
  struct Dog: AnyAnimal, SpeakingAnimal {
    // Она может лаять, что описано в реализации
    virtual void Speaking();
    // Она бегает, что описано в реализации
    virtual void Moving();
  };

  // Собака может лаять
  void Dog::Speaking() {
    printf("Караул! Воры лезут!: ");
    printf("Гав-гав\n");
  }
  // Она бегает
  void Dog::Moving() {
    printf("Я собака я бегу: ");
    printf("Чап-чап по лужам босиком\n");
  }

  //------------------------------------------------------------------------------
  // Это пингвин
  struct Penguin: AnyAnimal, SwimmingAnimal, SpeakingAnimal {
    // Он может ходит непонятно как (по умолчанию),
    // Зато он конкретно плавает, а другие здесь просто лохи
    virtual void Swimming();
    // И орет как никто
    virtual void Speaking();
  };

  // Пингвин может кричать, что описано в реализации
  void Penguin::Speaking() {
    printf("Винда маст дай! ");
    printf("Линукс рулит!\n");
  }
  // Он еще и ходит, но как - не знаем. Поэтому используется вариант по умолчанию.
  // Он также плавает и ныряет, а другие здесь просто лохи
  void Penguin::Swimming() {
    printf("Я пингвин. Я нырнул за рыбкой: ");
    printf("Буль-буль\n");
  }

  //------------------------------------------------------------------------------
  // Это червяк
  struct Worm: AnyAnimal {
    // Он молчит. По крайней мере мы не знаем как он поет. Убираем голос
    // Но мы знаем как он перемещается.
    virtual void Moving();
  };

  // Мы знаем как перемещаетсяч червяк.
  void Worm::Moving() {
    printf("Я червяк. Живу в чьем-то желудке: ");
    printf("Ползу в полной темноте\n");
  }

Общий тест из этого кода ("all/animals/01-start/oop-interface/main.cpp") смотрится следующим образом.


  //------------------------------------------------------------------------------
  int main(int argc, char* argv[]) {
    Elephant  e;
    Dog       d;
    Penguin   p;
    Worm      w;

    printf("==== Использование общей группы животных ====\n");

    printf("--- Прямое использование специализаций ---\n");
    e.Moving();
    d.Moving();
    p.Moving();
    w.Moving();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    AnyAnimal* any;
    any =  &e;
    any->Moving();
    any =  &d;
    any->Moving();
    any =  &p;
    any->Moving();
    any =  &w;
    any->Moving();

    printf("\n==== Использование группы пловцов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    p.Swimming();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SwimmingAnimal* swimmer;
    swimmer =  &p;
    swimmer->Swimming();

    printf("\n==== Использование группы говорунов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    e.Speaking();
    d.Speaking();
    p.Speaking();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SpeakingAnimal* speaker;
    speaker =  &e;
    speaker->Speaking();
    speaker =  &d;
    speaker->Speaking();
    speaker =  &p;
    speaker->Speaking();

    return 0;
  }

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

Процедурный подход

Использование процедурного подхода при решении той же задачи (каталог архива "all/animals/01-start/pp/") изначально может быть не связано с обобщенным восприятием всех животных как одной классификационной схемы. Скорее всего можно сразу заниматься животными и описывать их возможности в виде независимых функций. Как их потом разводить по общим свойствам? Это уже другой подход к построению обобщений.


  //------------------------------------------------------------------------------
  // Это слон
  typedef struct Elephant {} Elephant;
  // Слон может издавать звуки
  void ElephantSpeaking(Elephant* e) {printf("Ду-ду\n");}
  // Он топает при ходьбе
  void ElephantMoving(Elephant* e) {printf("Топ-топ\n");}

  //------------------------------------------------------------------------------
  // Это собака
  typedef struct Dog {} Dog;
  // Собака может лаять
  void DogSpeaking(Dog* d) {printf("Гав-гав\n");}
  // Она бегает
  void DogMoving(Dog* d) {printf("Чап-чап по лужам босиком\n");}

  //------------------------------------------------------------------------------
  // Это пингвин
  typedef struct Penguin {} Penguin;
  // Пингвин может кричать, что описано в реализации прототипа
  void PenguinSpeaking(Penguin* p) {printf("Линукс рулит!\n");}
  // Предположим, мы не знаем как он ходит, но знаем, что он ходит.
  // Зато он плавает и ныряет, а другие здесь просто лохи
  void PenguinSwimming(Penguin* p) {printf("Буль-буль\n");}

  //------------------------------------------------------------------------------
  // Это червяк
  typedef struct Worm {} Worm;
  // Он молчит. По крайней мере мы не знаем как он поет. Убираем голос
  // Но мы знаем как он перемещается.
  void WormMoving(Worm* w) {printf("Ползу в полной темноте\n");}

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


  //------------------------------------------------------------------------------
  // Для идентификации животных введем перечислимый тип
  typedef enum Animal {
    elephant,
    dog,
    penguin,
    worm,
  } Animal;

  //------------------------------------------------------------------------------
  // Структура, обобщающая можно описать до и после конкретных животных.
  // Построена на объединение указателей
  // Все, кто сверху - животные. Поэтому добавляем
  // Почему через указатель? Потому что одно животное-экземпляр можно добавить
  // к разным обобщениям
  typedef struct AnyAnimal {
    Animal animal;      // Признак животного
    union {
      Elephant *elephant;
      Dog      *dog;
      Penguin  *penguin;
      Worm     *worm;
    };
  } AnyAnimal;

  // Все животные могут иметь общие признаки, но с разными способами реализации
  // Например, перемещаться. Даже если мы не знаем как. Обобщим перемещение.
  // В ней пока представлено только перемещение
  void Moving(AnyAnimal* a) {
    switch(a->animal) {
      case elephant:  // Обработчик перемещения слона
        printf("Я есть слон. Я топаю: ");
        ElephantMoving(a->elephant);
        break;
      case dog:       // Обработчик перемещения собаки
        printf("Я собака я бегу: ");
        DogMoving(a->dog);
        break;
      case worm:      // Обработчик перемещения червя
        printf("Я червяк. Живу в чьем-то желудке: ");
        WormMoving(a->worm);
        break;
      default:
        // Обобщающая функция задает перемещение,
        // включая по умолчанию, когда не знаем способ
        printf("Я нечто. Как-то двигаюсь, но никто не знает как\n");
    }
  }
  // Если же какое-то поведение не соответствует всем животным, то это поведение
  // не включаем в это обобщение для всех. Обработается по default

  //------------------------------------------------------------------------------
  // Но можно создать обобщение для ограниченной группы животных, например,
  // только для плавающих.
  // Пока только пингвин. Поэтому включаем в эту группу только его
  typedef struct SwimmingAnimal {
    Animal animal;      // Признак животного
    union {
      Penguin  *penguin;
    };
  } SwimmingAnimal;

  // Для этой группы своя обобщающая функция
  // Если что-то пойдет не так, то просто вывалится из программы в отличие от
  // наличия обработчика по умолчанию.
  void Swimming(SwimmingAnimal* s) {
    switch(s->animal) {
      case penguin:  // Обработчик для пингвина. Он пока один в группе
        printf("Я пингвин. Я нырнул за рыбкой: ");
        PenguinSwimming(s->penguin);
        break;
      default:
        printf("В этой группе каждый знает плавание. Поэтому конец связи!\n");
        exit(-1);
    }
  }

  //------------------------------------------------------------------------------
  // И еще одна общая группа тех, кто что-то издает. Видимо, без червяка
  // Пока здесь следующий состав
  typedef struct SpeakingAnimal {
    Animal animal;      // Признак животного
    union {
      Elephant *elephant;
      Dog      *dog;
      Penguin  *penguin;
    };
  } SpeakingAnimal;

  // Для этой группы своя обобщающая функция
  void Speaking(SpeakingAnimal* s) {
    switch(s->animal) {
      case elephant:  // Обработчик звучания слона
        printf("Я слон. Я иду и пою: ");
        ElephantSpeaking(s->elephant);
        break;
      case dog:       // Обработчик звучания собаки
        printf("Караул! Воры лезут!: ");
        DogSpeaking(s->dog);
        break;
      case penguin:   // Обработчик звучания пингвина
        printf("Винда маст дай! ");
        PenguinSpeaking(s->penguin);
        break;
      default:
        // Обобщающая функция задает звучание по умолчанию, когда не знаем способ
        printf("Я нечто. Но я пою как никто. Кто я?\n");
    }
  }

Тестовый код процедурной программы порождает выход, аналогичный программе, реализованной с использованием ООП.


  //------------------------------------------------------------------------------
  int main(int argc, char* argv[]) {
    Elephant  e;
    Dog       d;
    Penguin   p;
    Worm      w;

    printf("==== Использование общей группы животных ====\n");

    printf("--- Прямое использование специализаций ---\n");
    AnyAnimal ae = {elephant, .elephant=&e};
    AnyAnimal ad = {dog,      .dog=&d};
    AnyAnimal ap = {penguin,  .penguin=&p};
    AnyAnimal aw = {worm,     .worm=&w};

    Moving(&ae);
    Moving(&ad);
    Moving(&ap);
    Moving(&aw);

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    AnyAnimal* any;
    any =  &ae;
    Moving(any);
    any =  &ad;
    Moving(any);
    any =  ≈
    Moving(any);
    any =  &aw;
    Moving(any);

    printf("\n==== Использование группы пловцов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    SwimmingAnimal sp = {penguin, .penguin=&p};
    Swimming(&sp);

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SwimmingAnimal* swimmer;
    swimmer =  &sp;
    Swimming(swimmer);

    printf("\n==== Использование группы говорунов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    SpeakingAnimal spe = {elephant, .elephant=&e};
    Speaking(&spe);
    SpeakingAnimal spd = {dog,      .dog=&d};
    Speaking(&spd);
    SpeakingAnimal spp = {penguin,  .penguin=&p};
    Speaking(&spp);

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SpeakingAnimal* speaker;
    speaker =  &spe;
    Speaking(speaker);
    speaker =  &spd;
    Speaking(speaker);
    speaker =  &spp;
    Speaking(speaker);

    return 0;
  }

ООП в процедурном стиле

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


  //------------------------------------------------------------------------------
  // Это слон
  struct Elephant {
    // Он может издавать звуки, что описано в реализации
    void Speaking();
    // Он топает при ходьбе, что описано в реализации
    void Moving();
  };
  // Слон может издавать звуки
  void Elephant::Speaking() {printf("Ду-ду\n");}
  // Он топает при ходьбе
  void Elephant::Moving() {printf("Топ-топ\n");}

  //------------------------------------------------------------------------------
  // Это собака
  struct Dog {
    // Она может лаять, что описано в реализации
    void Speaking();
    // Она бегает, что описано в реализации
    void Moving();
  };
  // Собака может лаять
  void Dog::Speaking() {printf("Гав-гав\n");}
  // Она бегает
  void Dog::Moving() {printf("Чап-чап по лужам босиком\n");}

  //------------------------------------------------------------------------------
  // Это пингвин
  struct Penguin {
    // Он может кричать, что описано в реализации прототипа
    void Speaking();
    // Предположим, мы не знаем как он ходит, но знаем, что он ходит.
    // Поэтому оставляем в ходячих по умолчанию
    // Зато он плавает и ныряет, а другие здесь просто лохи
    void Swimming();
  };
  // Пингвин может кричать, что описано в реализации
  void Penguin::Speaking() {printf("Линукс рулит!\n");}
  // Предположим, мы не знаем как он ходит, но знаем, что он ходит.
  // Зато он плавает и ныряет, а другие здесь просто лохи
  void Penguin::Swimming() {printf("Буль-буль\n");}
  //------------------------------------------------------------------------------
  // Это червяк
  struct Worm {
    // Он молчит. По крайней мере мы не знаем как он поет. Убираем голос
    // Но мы знаем как он перемещается.
    void Moving();
  };
  // Мы знаем как он перемещается.
  void Worm::Moving() {printf("Ползу в полной темноте\n");}

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


  //------------------------------------------------------------------------------
  // Класс, обобщающий всех животных.
  struct AnyAnimal {
    // Все животные могут иметь общие признаки.
    // Например, перемещаться. Даже если мы не знаем как. Обобщим перемещение.
    // Для этого используем виртуальный обработчик по умолчанию
    virtual void Moving();
  };
  // Пока представлено только перемещение
  void AnyAnimal::Moving() {
    // Обобщающая функция задает перемещение,
    // включая по умолчанию, когда не знаем способ
    printf("Я нечто. Как-то двигаюсь, но никто не знает как\n");
  }

  // Слон, принадлежащий к группе всех животных
  struct ElephantAsAnimal:AnyAnimal {
    Elephant* elephant;
    virtual void Moving(); // Знаем, как он ходит
    ElephantAsAnimal(Elephant* e):elephant(e){}
  };
  // Обработчик перемещения слона
  void ElephantAsAnimal::Moving() {
    printf("Я есть слон. Я топаю: ");
    elephant->Moving();
  }

  // Собака, принадлежащая к группе всех животных
  struct DogAsAnimal:AnyAnimal {
    Dog* dog;
    virtual void Moving(); // Знаем, как она бегает
    DogAsAnimal(Dog* d):dog(d){}
  };
  // Обработчик перемещения собаки
  void DogAsAnimal::Moving() {
    printf("Я собака я бегу: ");
    dog->Moving();
  }

  // Пингвин, принадлежащий к группе всех животных
  struct PenguinAsAnimal:AnyAnimal {
    Penguin* penguin;
    // Перемещение по умолчанию. Мы не видели, как пингвин ходит
    PenguinAsAnimal(Penguin* p):penguin(p){}
  };

  // Червяк, принадлежащий к группе всех животных
  struct WormAsAnimal:AnyAnimal {
    Worm* worm;
    virtual void Moving(); // Знаем, как он ползает
    WormAsAnimal(Worm* w):worm(w){}
  };
  // Обработчик перемещения червя
  void WormAsAnimal::Moving() {
    printf("Я червяк. Живу в чьем-то желудке: ");
    worm->Moving();
  }

  //------------------------------------------------------------------------------
  // Обобщение для ограниченной группы животных, например, для плавающих.
  // Пока только пингвин. Поэтому включаем в эту группу только его
  struct SwimmingAnimal {
    // Для этой группы своя обобщающая функция. Пусть будет абстрактной
    virtual void Swimming() = 0;
  };

  // Пингвин, принадлежащий к пловцам
  struct PenguinAsSwimmingAnimal:SwimmingAnimal {
    Penguin* penguin;
    virtual void Swimming(); // Знаем, как он плавает
    PenguinAsSwimmingAnimal(Penguin* p):penguin(p){}
  };
  // Определяется только пингвин. Других нет
  void PenguinAsSwimmingAnimal::Swimming() {
    printf("Я пингвин. Я нырнул за рыбкой: ");
    penguin->Swimming();
  }

  //------------------------------------------------------------------------------
  // Группа тех, кто издает звуки. Видимо, без червяка
  struct SpeakingAnimal {
    // Для этой группы своя обобщающая функция. Обработчик по умолчанию
    virtual void Speaking();
  };
  void SpeakingAnimal::Speaking() {
    // Обобщающая функция задает звучание по умолчанию, когда не знаем способ
    printf("Я нечто. Но я пою как никто. Кто я?\n");
  }

  // Слон, принадлежащий к группе поющих
  struct ElephantAsSpeakingAnimal:SpeakingAnimal {
    Elephant* elephant;
    virtual void Speaking(); // Знаем, как он дудит
    ElephantAsSpeakingAnimal(Elephant* e):elephant(e){}
  };
  // Обработчик звучания слона
  void ElephantAsSpeakingAnimal::Speaking() {
    printf("Я слон. Я иду и пою: ");
    elephant->Speaking();
  }

  // Собака, принадлежащая к группе звучащих
  struct DogAsSpeakingAnimal:SpeakingAnimal {
    Dog* dog;
    virtual void Speaking(); // Знаем, как она лает
    DogAsSpeakingAnimal(Dog* d):dog(d){}
  };
  // Обработчик звучани собаки
  void DogAsSpeakingAnimal::Speaking() {
    printf("Караул! Воры лезут!: ");
    dog->Speaking();
  }

  // Пингвин, принадлежащий к группе всех животных
  struct PenguinAsSpeakingAnimal:SpeakingAnimal {
    Penguin* penguin;
    virtual void Speaking(); // Знаем, как он хамит Гейтсу
    PenguinAsSpeakingAnimal(Penguin* p):penguin(p){}
  };
  // Обработчик звучания пингвина
  void PenguinAsSpeakingAnimal::Speaking() {
    printf("Винда маст дай! ");
    penguin->Speaking();
  }

Главная функция обеспечивает реализацию теста, аналогичного предшествующим.


  //------------------------------------------------------------------------------
  int main(int argc, char* argv[]) {
    Elephant  e;
    Dog       d;
    Penguin   p;
    Worm      w;

    printf("==== Использование общей группы животных ====\n");

    printf("--- Прямое использование специализаций ---\n");
    ElephantAsAnimal ae{&e};
    DogAsAnimal ad{&d};
    PenguinAsAnimal ap{&p};
    WormAsAnimal aw{&w};

    ae.Moving();
    ad.Moving();
    ap.Moving();
    aw.Moving();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    AnyAnimal* any;
    any =  &ae;
    any->Moving();
    any =  &ad;
    any->Moving();
    any =  ≈
    any->Moving();
    any =  &aw;
    any->Moving();

    printf("\n==== Использование группы пловцов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    PenguinAsSwimmingAnimal sp{&p};
    sp.Swimming();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SwimmingAnimal* swimmer;
    swimmer =  &sp;
    swimmer->Swimming();

    printf("\n==== Использование группы говорунов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    ElephantAsSpeakingAnimal spe{&e};
    spe.Speaking();
    DogAsSpeakingAnimal spd{&d};
    spd.Speaking();
    PenguinAsSpeakingAnimal spp{&p};
    spp.Speaking();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SpeakingAnimal* speaker;
    speaker =  &spe;
    speaker->Speaking();
    speaker =  &spd;
    speaker->Speaking();
    speaker =  &spp;
    speaker->Speaking();

    return 0;
  }

Процедурно-параметрическая реализация

Одной из ключевых особенностей ПП подхода является то, что к проектированию обобщений и специализаций можно подходить с различных сторон, а не только сверху или снизу. Неважно, что первым пришло в голову: классификация признаков, задающих поведение животных или их список, используемый для реализации. Во втором случае описание типов данных и функций практически полностью аналогично процедурному подходу (каталог архива "all/animals-reuse/01-start/").


  //------------------------------------------------------------------------------
  // Описание животных и их поведений.

  //------------------------------------------------------------------------------
  // Это слон
  typedef struct Elephant {} Elephant;
  // Слон может издавать звуки
  void ElephantSpeaking(Elephant* e) {printf("Ду-ду\n");}
  // Он топает при ходьбе
  void ElephantMoving(Elephant* e) {printf("Топ-топ\n");}

  //------------------------------------------------------------------------------
  // Это собака
  typedef struct Dog {} Dog;
  // Собака может лаять
  void DogSpeaking(Dog* d) {printf("Гав-гав\n");}
  // Она бегает
  void DogMoving(Dog* d) {printf("Чап-чап по лужам босиком\n");}

  //------------------------------------------------------------------------------
  // Это пингвин
  typedef struct Penguin {} Penguin;
  // Пингвин может кричать, что описано в реализации прототипа
  void PenguinSpeaking(Penguin* p) {printf("Линукс рулит!\n");}
  // Предположим, мы не знаем как он ходит, но знаем, что он ходит.
  // Зато он плавает и ныряет, а другие здесь просто лохи
  void PenguinSwimming(Penguin* p) {printf("Буль-буль\n");}

  //------------------------------------------------------------------------------
  // Это червяк
  typedef struct Worm {} Worm;
  // Он молчит. По крайней мере мы не знаем как он поет. Убираем голос
  // Но мы знаем как он перемещается.
  void WormMoving(Worm* w) {printf("Ползу в полной темноте\n");}

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


  //------------------------------------------------------------------------------
  // Структура, обобщающая конкретных животных. Можно описать до и после
  // Не важно. Она пока пустая
  typedef struct AnyAnimal {} <> AnyAnimal;
  // Все, кто сверху - животные. Поэтому добавляем
  // Почему через указатель? Потому что одно животное-экземпляр можно добавить
  // к разным обобщениям
  AnyAnimal + <elephant: Elephant*;>;
  AnyAnimal + <dog: Dog*;>;
  AnyAnimal + <penguin: Penguin*;>;
  AnyAnimal + <worm: Worm*;>;

  // Обобщающая функция задает перемещение по умолчанию, когда не знаем способ
  // void Moving<AnyAnimal* a>() = 0;
  void Moving<AnyAnimal* a>() {
    printf("Я нечто. Как-то двигаюсь, но никто не знает как\n");
  }
  // Обработчик перемещения слона
  void Moving<AnyAnimal.elephant *a>() {
    printf("Я есть слон. Я топаю: ");
    ElephantMoving(a->@);
  }
  // Обработчик перемещения собаки
  void Moving<AnyAnimal.dog* a>() {
    printf("Я собака я бегу: ");
    DogMoving(a->@);
  }
  // Обработчик перемещения червя
  void Moving<AnyAnimal.worm* a>() {
    printf("Я червяк. Живу в чьем-то желудке: ");
    WormMoving(a->@);
  }

  //------------------------------------------------------------------------------
  // Обобщение для ограниченной группы животных, например, только для плавающих.
  typedef struct SwimmingAnimal {} <> SwimmingAnimal;
  // Пока только пингвин. Поэтому включаем в эту группу только его
  SwimmingAnimal + <penguin: Penguin*;>;

  // Абстрактная обобщающая функция. Требует обязательного описания
  // всех обработчиков специализаций
  void Swimming<SwimmingAnimal* s>() = 0;
  // Обработчик для пингвина. Он пока один в группе
  void Swimming<SwimmingAnimal.penguin* s>() {
    printf("Я пингвин. Я поплыл за рыбкой: ");
    PenguinSwimming(s->@);
  }

  //------------------------------------------------------------------------------
  // И еще одна общая группа тех, кто что-то издает. Видимо, без червяка
  typedef struct SpeakingAnimal {} <> SpeakingAnimal;
  // Пока здесь следующий состав
  SpeakingAnimal + <elephant: Elephant*;>;
  SpeakingAnimal + <dog: Dog*;>;
  SpeakingAnimal + <penguin: Penguin*;>;

  // Обобщающая функция задает звучание по умолчанию, когда не знаем способ
  void Speaking<SpeakingAnimal* s>() {
    printf("Я нечто. Но я пою как никто. Кто я?\n");
  }
  // Обработчик звучания слона
  void Speaking<SpeakingAnimal.elephant* s>() {
    printf("Я слон. Я иду и пою: ");
    ElephantSpeaking(s->@);
  }
  // Обработчик звучания собаки
  void Speaking<SpeakingAnimal.dog* s>() {
    printf("Караул! Воры лезут!: ");
    DogSpeaking(s->@);
  }
  // Обработчик звучания пингвина
  void Speaking<SpeakingAnimal.penguin* s>() {
    printf("Винда маст дай! ");
    PenguinSpeaking(s->@);
  }

Тестовый код, реализует те же действия, что и в предыдущих примерах.


  //------------------------------------------------------------------------------
  int main(int argc, char* argv[]) {
    Elephant  e;
    Dog       d;
    Penguin   p;
    Worm      w;

    printf("==== Использование общей группы животных ====\n");

    printf("--- Прямое использование специализаций ---\n");
    struct AnyAnimal.elephant ae;   ae.@ = &e;
    struct AnyAnimal.dog      ad;   ad.@ = &d;
    struct AnyAnimal.penguin  ap;   ap.@ = &p;
    struct AnyAnimal.worm     aw;   aw.@ = &w;

    Moving<&ae>();
    Moving<&ad>();
    Moving<&ap>();
    Moving<&aw>();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    AnyAnimal* any;
    any =  &ae;
    Moving<any>();
    any =  &ad;
    Moving<any>();
    any =  &ap;
    Moving<any>();
    any =  &aw;
    Moving<any>();

    printf("\n==== Использование группы пловцов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    struct SwimmingAnimal.penguin  sp;   sp.@ = &p;
    Swimming<&sp>();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SwimmingAnimal* swimmer;
    swimmer =  &sp;
    Swimming<swimmer>();

    printf("\n==== Использование группы говорунов ====\n");
    printf("--- Прямое использование специализаций ---\n");
    struct SpeakingAnimal.elephant  spe;   spe.@ = &e;
    Speaking<&spe>();
    struct SpeakingAnimal.dog  spd;   spd.@ = &d;
    Speaking<&spd>();
    struct SpeakingAnimal.penguin  spp;   spp.@ = &p;
    Speaking<&spp>();

    printf("--- Использование специализаций через обобщенный указатель ---\n");
    SpeakingAnimal* speaker;
    speaker =  &spe;
    Speaking<speaker>();
    speaker =  &spd;
    Speaking<speaker>();
    speaker =  &spp;
    Speaking<speaker>();

    return 0;
  }

Хаотическое расширение программы

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

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

  2. Мы вдруг вспомнили, что пингвин может еще и нырять. Возникают вопросы:
    • Куда отнести ныряние? Стоит ли включать его в другой интерфейс или создать новый (не все ныряют)?
    • Как оно будет интегрироваться с другой функциональностью?
  3. Появилось желание добавить в общие свойства всех животных еще одну альтернативную функциональность. Например, рассмотреть вариант, когда каждое из животных ест определенную пищу. Каждое ест по своему (не делать обработчик по умолчанию).

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

  5. Захотелось реализовать мультиметод. Для каждого варианта отношений придумать свой функционал. Будем выводить какую-то нестандартную фразу для каждой из комбинации животных. Например, для двух аргументов:

    • Слон - собака. А он себе идет и лаю твоего не замечает.
    • Собака - слон. Ай Моська, знать она сильна, что лает на слона.
  6. Решили добавить новое живое существо. Можно добавить, например, комара, рыбу, орла и т.д. С появлением комара или орла появится необходимость ввода функционала полета. Ну а далее могут быть бабочка и прочие.

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

Добавляем плавание для собаки

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

Во вновь формируемом проекте (каталог архива "all/animals-reuse/02-swimming-dog/") эти телодвижения выльются в следующих код, размещенный в дополнительных заголовочном файле и единице компиляции.


  // Описание специализации для собаки-пловца
  SwimmingAnimal + <dog: Dog*;>;

  // Плавание собаки можно прописать здесь или прямо в обработчике специализации
  void DogSwimming(Dog *d)  {printf("Я плыву по собачьи\n");}
  // Обработчик для собаки, которая добавлена в группу к пингвину
  void Swimming<SwimmingAnimal.dog* s>() {
    printf("Я собака. Я тоже могу плыть: ");
    DogSwimming(s->@);
  }

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


  printf("\n==== Использование группы пловцов ====\n");
  printf("--- Прямое использование специализаций ---\n");
  ...
  struct SwimmingAnimal.dog  sd;   sd.@ = &d;
  Swimming<&sd>();

  printf("--- Использование специализаций через обобщенный указатель ---\n");
  SwimmingAnimal* swimmer;
  ...
  swimmer =  &sd;
  Swimming<swimmer>();

Добавление ныряющего пингвина

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

Остается реализовать соответствующую обобщающую функцию, обработчик специализации, расширить тест, и проект готов (каталог архива all/animals-reuse/03-diving-penguin/). Все остальные необходимые файлы подключаются из двух предыдущих проектов.


  //------------------------------------------------------------------------------
  // Обобщение для группы ныряльщиков
  typedef struct DivingAnimal {} <> DivingAnimal;
  // Пока только пингвин. Поэтому включаем в эту группу только его
  DivingAnimal + <penguin: Penguin*;>;

  // Пингвин ныряет, а другие смотрят и завидуют
  void PenguinDiving(Penguin* p) {printf("Вниз. На глубину\n");}

  // Абстрактная обобщающая функция. Требует обязательного описания
  // всех обработчиков специализаций
  void Diving<DivingAnimal* s>() = 0;
  // Обработчик для пингвина. Он пока один в группе
  void Diving<DivingAnimal.penguin* s>() {
    printf("Я пингвин. Я нырнул: ");
    PenguinDiving(s->@);
  }

  // Где-то в тесте добавили проверку...
  ...
  printf("\n==== Использование группы ныряльщиков ====\n");
  printf("--- Прямое использование специализаций ---\n");
  struct DivingAnimal.penguin  dp;   dp.@ = &p;
  Diving<&dp>();

  printf("--- Использование специализаций через обобщенный указатель ---\n");
  DivingAnimal* diver;
  diver =  &dp;
  Diving<diver>();
  ...

Все кушать хотят. Добавляем едоков

Итак, в какой-то момент, когда желудок напомнил о себе, нас осенило, что и наши животные должны утолять голод. Поэтому проект снова приходится расширять (каталог архива all/animals-reuse/04-eating/) за счет добавления в него соответствующей функциональности.

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


  //==============================================================================
  // Добавляются функции, реализующие поедание отдельными животными
  //==============================================================================
  // Слон ест бананы
  void ElephantEating(Elephant* e) {printf("Достаю хоботом и ем бананы\n");}
  // Собака ест мясо
  void DogEating(Dog* d) {printf("Ем мясо из миски\n");}
  // Пингвин ест рыбу
  void PenguinEating(Penguin* p) {printf("Ловлю и ем рыбу\n");}
  // Червяк жрет землю
  void WormEating(Worm* w)  {printf("Жру землю\n");}

  //==============================================================================
  // Обработчики специализаций для группы, включающей пловцов
  //==============================================================================
  // Обобщающая функция для всех едоков
  void Eating<AnyAnimal* a>() = 0;  // Обязательно своя для каждого едока

  // Обработчик питания слона
  void Eating<AnyAnimal.elephant *a>() {
    printf("Мой хобот - моя рука: ");
    ElephantEating(a->@);
  }
  // Обработчик питания собаки
  void Eating<AnyAnimal.dog* a>() {
    printf("Хозяин принес еду: ");
    DogEating(a->@);
  }
  // Обработчик питания пингвина
  void Eating<AnyAnimal.penguin* a>() {
    printf("Поплыл рыбачить: ");
    PenguinEating(a->@);
  }
  // Обработчик питания червя
  void Eating<AnyAnimal.worm* a>() {
    printf("Я земляной червь: ");
    WormEating(a->@);
  }

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


  ...
  printf("\n==== Использование общей группы животных. Питание ====\n");

  printf("--- Прямое использование специализаций ---\n");
  Eating<&ae>();
  Eating<&ad>();
  Eating<&ap>();
  Eating<&aw>();

  printf("--- Использование специализаций через обобщенный указатель ---\n");
  any =  &ae;
  Eating<any>();
  any =  &ad;
  Eating<any>();
  any =  &ap;
  Eating<any>();
  any =  &aw;
  Eating<any>();
  ...

Вспоминаим классификацию из зоологии...

Ну и как же обойтись без знаний из учебника зоологии? При наличии разнообразных животных хорошо бы определиться с их принадлежностью к различным классам. Для этого опять нужно расширить проект (каталог архива all/animals-reuse/05-groups/), внеся в него новые понятия и описав соответствующие функции.

Сформируем различные классы животных.


  // Каждая из групп может включать конкретных животных, добавляемых в процессе
  // их появления в системе. Пок подключены только те, что уже есть
  typedef struct MammalGroup{}<> MammalGroup;   // млекопитающее
  typedef struct BirdGroup{}<> BirdGroup;       // птицы
  typedef struct WormGroup{}<> WormGroup;       // черви

  // Эти группы отсутствуют но могут быть расширены в других единицах
  // компиляции при появлении соответствующих животных. Остались для справок
  // MolluskGroup    // моллюск
  // ракообразный    PagurianGroup
  // паукообразный   SpideryGroup
  // насекомые       InsectGroup
  // рыба            FishGroup
  // земноводное     AmphibiaGroup
  // пресмыкающееся  ReptileGroup

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


  // Добавление животных в группы, к которым они принадлежат
  // Животные должны входить в общую группу животных
  // MammalGroup + <elephant: AnyAnimal.elephant;>;
  // MammalGroup + <dog: AnyAnimal.dog;>;
  // BirdGroup + <penguin: AnyAnimal.penguin;>;
  // WormGroup + <worm: AnyAnimal.worm;>;

  // Прямое подключение специализаций пока не работает. Но будет. Работаем...
  // Обходное решение. Группы задают с перечислением.
  MammalGroup + <elephant: void;>;
  MammalGroup + <dog: void;>;
  BirdGroup + <penguin: void;>;
  WormGroup + <worm: void;>;

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


  // Отдельные группы включаются в общий классификатор групп
  typedef struct AnimalGroups {}<> AnimalGroups;
  // Те группы, которые уже есть, можно подключить сразу
  // Признак определяется именем типа. Включение непосредственное.
  AnimalGroups + <MammalGroup;>;
  AnimalGroups + <BirdGroup;>;
  AnimalGroups + <WormGroup;>;

  //------------------------------------------------------------------------------
  // Вывод информации о группе, к которой принадлежит животное
  // Непосредственно для обобщения информацию выводить запрещено.
  // Выводится сообщение об ошибке и происходит прерывание работы программы.
  void AnimalGroupInfo<AnimalGroups* ag>() {
    printf("Обобщенная группа без специализации запрещена! Измените код!\n");
    exit(1);
  }
  //  Обработчик для млекопитающих
  void AnimalGroupInfo<AnimalGroups.MammalGroup* ag>() {
    printf("Я есть млекопитающий\n");
  }
  //  Обработчик для птиц
  void AnimalGroupInfo<AnimalGroups.BirdGroup* ag>() {
    printf("Я птица однако\n");
  }
  //  Обработчик для червей
  void AnimalGroupInfo<AnimalGroups.WormGroup* ag>() {
    printf("Червяк я\n");
  }

  //------------------------------------------------------------------------------
  // Получение информации о группе через обобщенного животного
  // Используется прямое решение через полиморфные функции

  // Обобщающая функция запрещает обработку
  void Group<AnyAnimal* a>() {
    printf("Для обобщенного животного группа отсутствует! Измените код!\n");
    exit(1);
  }
  // Обработчик группы для слона
  void Group<AnyAnimal.elephant* a>() {
    printf("Привет, я розовый слоненок. ");
    struct AnimalGroups.MammalGroup g;
    AnimalGroupInfo<&g>();
  }
  // Обработчик группы для собаки
  void Group<AnyAnimal.dog* a>() {
    printf("Я маленький щенок. ");
    struct AnimalGroups.MammalGroup g;
    AnimalGroupInfo<&g>();
  }
  // Обработчик группы для пингвина
  void Group<AnyAnimal.penguin* a>() {
    printf("Пингвин окошкам не товарищ. ");
    struct AnimalGroups.BirdGroup g;
    AnimalGroupInfo<&g>();
  }
  // Обработчик группы для червя
  void Group<AnyAnimal.worm* a>() {
    printf("Моя задача - строить тоннели. ");
    struct AnimalGroups.WormGroup g;
    AnimalGroupInfo<&g>();
  }

И конечно то, что получилось, добавляется в общий тест.


  printf(
    "\n==== Определение принадлежности животных к конкретной группе ====\n"
  );

  printf("--- Прямое использование специализаций ---\n");
  Group<(AnyAnimal*)&ae>();
  Group<(AnyAnimal*)&ad>();
  Group<(AnyAnimal*)&ap>();
  Group<(AnyAnimal*)&aw>();

  printf("--- Использование специализаций через обобщенный указатель ---\n");
  any =  (AnyAnimal*)&ae;
  Group<any>();
  any =  (AnyAnimal*)&ad;
  Group<any>();
  any =  (AnyAnimal*)&ap;
  Group<any>();
  any =  (AnyAnimal*)&aw;
  Group<any>();

Мультиметод

Добавление мультиметода в проект расширяет рассмотрение отдельных животных до их взаимодействия. Например, в режиме один на один. Это ведет к созданию обработчиков двух полиморфных аргументов (каталог архива all/animals-reuse/06-multimethod/), в которых перебираются различные комбинации конкретных животных. Использование множественного полиморфизма уже многократно встречалось в других материалах. Поэтому все очень просто. Например, можно сформировать вычисления непосредственно в обработчиках специализаций. Особенно в тех случаях, когда они небольшие (как и в этой задаче). Это ускорит процесс за счет уменьшения вызовов функций. Но можно для каждой пары основ специализаций сделать отдельную обработку, повысив модульность программы. Что и представлено ниже.


  // Слон и слон
  void MultimethodElephantElephant(Elephant* e1, Elephant* e2) {
    printf("Хоть мы и слоны, но до тебя доходит как до жирафа. ППП рулит!\n");
  }
  // Слон и собака
  void MultimethodElephantDog(Elephant* e, Dog* d) {
    printf("Моська, мне Слону на тебя начхать.\n");
  }
  // Слон и пингвин
  void MultimethodElephantPenguin(Elephant* e, Penguin* p) {
    printf("Слон с Пингвином разделены океаном\n");
  }
  // Слон и червяк
  void MultimethodElephantWorm(Elephant* e, Worm* w) {
    printf("Слон Червяку: Извини. Растоптал и не заметил.\n");
  }

  // Собака и слон
  void MultimethodDogElephant(Dog* d, Elephant* e) {
    printf("Я Моська! Я Сильна! Я лаю на слона!\n");
  }
  // Собака и собака
  void MultimethodDogDog(Dog* d1, Dog* d2) {
    printf("Две собаки на охоте. За котами...\n");
  }
  // Собака и пингвин
  void MultimethodDogPenguin(Dog* d, Penguin* p) {
    printf("Собака пингвину: поплыли до того берега этого ручья\n");
  }
  // Собака и червяк
  void MultimethodDogWorm(Dog* d, Worm* w) {
    printf("Собака червей не ест.\n");
  }

  // Пингвин и слон
  void MultimethodPenguinElephant(Penguin* p, Elephant* e) {
    printf("Пингвин: Эй, слон плыви ко мне. Будем бить окна.\n");
  }
  // Пингвин и собака
  void MultimethodPenguinDog(Penguin* p, Dog* d) {
    printf("Пингвин собаке: поплыли до того берега этого океана\n");
  }
  // Пингвин и пингвин
  void MultimethodPenguinPenguin(Penguin* p1, Penguin* p2) {
    printf("Антарктида. Холодно пингвинам.\n");
  }
  // Пингвин и червяк
  void MultimethodPenguinWorm(Penguin* p, Worm* w) {
    printf("Пингвин в снегу не может отыскать червяка\n");
  }

  // Червяк и слон
  void MultimethodWormElephant(Worm* w, Elephant* e) {
    printf("Червяк: Слон, в твоем желудке столько бананов...\n");
  }
  // Червяк и собака
  void MultimethodWormDog(Worm* w, Dog* d) {
    printf("Червяк собаку не увидел.\n");
  }
  // Червяк и пингвин
  void MultimethodWormPenguin(Worm* w, Penguin* p2) {
    printf("Червяк не хочет жить в Антарктиде у пингвинов.\n");
  }
  // Червяк и червяк
  void MultimethodWormWorm(Worm* w1, Worm* w2) {
    printf("Червячок к червячку и на рыбалку.\n");
  }

  //------------------------------------------------------------------------------
  // Обобщающая функция, задающая интерфейс мультиметода
  void Multimethod<AnyAnimal* a1, AnyAnimal* a2>() {
    printf("Ошибка: Мультиметод от обобщений должен быть переопределен!\n");
    exit(2);
  }
  // Обработчик специализации для отношения слон - слон
  void Multimethod<AnyAnimal.elephant* a1, AnyAnimal.elephant* a2>() {
    MultimethodElephantElephant(a1->@, a2->@);
  }
  // Обработчик специализации для отношения слон - собака
  void Multimethod<AnyAnimal.elephant* a1, AnyAnimal.dog* a2>() {
    MultimethodElephantDog(a1->@, a2->@);
  }
  // Обработчик специализации для отношения слон - пингвин
  void Multimethod<AnyAnimal.elephant* a1, AnyAnimal.penguin* a2>() {
    MultimethodElephantPenguin(a1->@, a2->@);
  }
  // Обработчик специализации для отношения слон - червяк
  void Multimethod<AnyAnimal.elephant* a1, AnyAnimal.worm* a2>() {
    MultimethodElephantWorm(a1->@, a2->@);
  }

  // Обработчик специализации для отношения собака - слон
  void Multimethod<AnyAnimal.dog* a1, AnyAnimal.elephant* a2>() {
    MultimethodDogElephant(a1->@, a2->@);
  }
  // Обработчик специализации для отношения собака - собака
  void Multimethod<AnyAnimal.dog* a1, AnyAnimal.dog* a2>() {
    MultimethodDogDog(a1->@, a2->@);
  }
  // Обработчик специализации для отношения собака - пингвин
  void Multimethod<AnyAnimal.dog* a1, AnyAnimal.penguin* a2>() {
    MultimethodDogPenguin(a1->@, a2->@);
  }
  // Обработчик специализации для отношения собака - червяк
  void Multimethod<AnyAnimal.dog* a1, AnyAnimal.worm* a2>() {
    MultimethodDogWorm(a1->@, a2->@);
  }

  // Обработчик специализации для отношения пингвин - слон
  void Multimethod<AnyAnimal.penguin* a1, AnyAnimal.elephant* a2>() {
    MultimethodPenguinElephant(a1->@, a2->@);
  }
  // Обработчик специализации для отношения пингвин - собака
  void Multimethod<AnyAnimal.penguin* a1, AnyAnimal.dog* a2>() {
    MultimethodPenguinDog(a1->@, a2->@);
  }
  // Обработчик специализации для отношения пингвин - пингвин
  void Multimethod<AnyAnimal.penguin* a1, AnyAnimal.penguin* a2>() {
    MultimethodPenguinPenguin(a1->@, a2->@);
  }
  // Обработчик специализации для отношения пингвин - червяк
  void Multimethod<AnyAnimal.penguin* a1, AnyAnimal.worm* a2>() {
    MultimethodPenguinWorm(a1->@, a2->@);
  }

  // Обработчик специализации для отношения червяк - слон
  void Multimethod<AnyAnimal.worm* a1, AnyAnimal.elephant* a2>() {
    MultimethodWormElephant(a1->@, a2->@);
  }
  // Обработчик специализации для отношения червяк - собака
  void Multimethod<AnyAnimal.worm* a1, AnyAnimal.dog* a2>() {
    MultimethodWormDog(a1->@, a2->@);
  }
  // Обработчик специализации для отношения червяк - пингвин
  void Multimethod<AnyAnimal.worm* a1, AnyAnimal.penguin* a2>() {
    MultimethodWormPenguin(a1->@, a2->@);
  }
  // Обработчик специализации для отношения червяк - червяк
  void Multimethod<AnyAnimal.worm* a1, AnyAnimal.worm* a2>() {
    MultimethodWormWorm(a1->@, a2->@);
  }

И еще один тестовый набор для чистоты эксперимента.


  printf("\n==== Применение мультиметода ====\n");

  printf("--- Прямое использование специализаций ---\n");
  Multimethod<&ae, &ae>();
  Multimethod<&ae, &ad>();
  Multimethod<&ae, &ap>();
  Multimethod<&ae, &aw>();

  Multimethod<&ad, &ae>();
  Multimethod<&ad, &ad>();
  Multimethod<&ad, &ap>();
  Multimethod<&ad, &aw>();

  Multimethod<&ap, &ae>();
  Multimethod<&ap, &ad>();
  Multimethod<&ap, &ap>();
  Multimethod<&ap, &aw>();

  Multimethod<&aw, &ae>();
  Multimethod<&aw, &ad>();
  Multimethod<&aw, &ap>();
  Multimethod<&aw, &aw>();

  printf("--- Использование специализаций через обобщенный указатель ---\n");
  AnyAnimal* pae =  &ae;
  AnyAnimal* pad =  &ad;
  AnyAnimal* pap =  &ap;
  AnyAnimal* paw =  &aw;

  Multimethod<pae, pae>();
  Multimethod<pae, pad>();
  Multimethod<pae, pap>();
  Multimethod<pae, paw>();

  Multimethod<pad, pae>();
  Multimethod<pad, pad>();
  Multimethod<pad, pap>();
  Multimethod<pad, paw>();

  Multimethod<pap, pae>();
  Multimethod<pap, pad>();
  Multimethod<pap, pap>();
  Multimethod<pap, paw>();

  Multimethod<paw, pae>();
  Multimethod<paw, pad>();
  Multimethod<paw, pap>();
  Multimethod<paw, paw>();

Вдруг откуда-то летит маленький комарик...

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

Появление нового животного естественным образом влияет практически на все отношения, которые были представлены ранее, что и должно отразиться в формируемой версии проекта (каталог архива all/animals-reuse/07-mosquito/). Однако ничего такого, кардинально изменяющего ранее написанный код, не происходит. Добавление еще одного заголовочного файла и файла с реализациями функций, в которых и представлены все расширения. Комар издает звуки (в данном случае крыльями), летает и питается, что определяет его подключение к соответствующим обработчикам специализаций. Помимо этого появляется новый вид животных. И не стоит забывать про расширение мультиметода.


  // Это комар
  typedef struct Mosquito {} Mosquito;

  // Группа для перемещений и еды
  AnyAnimal + <mosquito: Mosquito*;>;

  // Группа для звучания
  SpeakingAnimal + <mosquito: Mosquito*;>;

  // Появление группы насекомых
  typedef struct InsectGroup {}<> InsectGroup;
  // Включение в нее комара
  InsectGroup + <mosquito: void;>;

  // Включение насекомых в общую группу
  AnimalGroups + <InsectGroup;>;

  // Обобщение для летающих.
  typedef struct FlyingAnimal {} <> FlyingAnimal;
  // Пока только комар
  FlyingAnimal + <mosquito: Mosquito*;>;

  //------------------------------------------------------------------------------
  // Комар может издавать звон
  void MosquitoSpeaking(Mosquito* m) {printf("З-з-з-з-з-з\n");}
  // Комар летает. Непонятно, может он ходит, хотя ноги есть.
  void MosquitoFlying(Mosquito* m) {printf("Все выше, к Карлсону на крыше.\n");}
  // И кровь он сосет. Кровосос!
  void MosquitoEating(Mosquito* m) {printf("Кровь! Свежая!!!\n");}

  //------------------------------------------------------------------------------
  // Обработчик специализации, определяющие комара, как говоруна
  void Speaking<SpeakingAnimal.mosquito *a>() {
    printf("Люблю позвенеть: ");
    MosquitoSpeaking(a->@);
  }

  //------------------------------------------------------------------------------
  // Обработчик специализации, определяющие комара, как едока
  void Eating<AnyAnimal.mosquito *a>() {
    printf("Мой хоботок - мой кормилец: ");
    MosquitoEating(a->@);
  }

  //------------------------------------------------------------------------------
  // Обработчик специализации для группы, включающей летунов
  // Абстрактная обобщающая функция. Требует обязательного описания
  // всех обработчиков специализаций
  void Flying<FlyingAnimal* f>() = 0;
  // Обработчик для комара. Он пока один в группе
  void Flying<FlyingAnimal.mosquito* f>() {
    printf("Я комар. Я полетел попить крови: ");
    MosquitoFlying(f->@);
  }

  //------------------------------------------------------------------------------
  // Обработчики обеспечивающие информацию о группе животны и принадлежность
  // комаров к группе насекомых
  //  Обработчик для насекомых
  void AnimalGroupInfo<AnimalGroups.InsectGroup* ag>() {
    printf("Это группа насекомых\n");
  }
  // Обработчик группы для комара
  void Group<AnyAnimal.mosquito* a>() {
    printf("Мне назначено судьбой пить кровь. ");
    struct AnimalGroups.InsectGroup g;
    AnimalGroupInfo<&g>();
  }

  //==============================================================================
  // Мультиметоды, реализующие отношения между комаром и другими животными
  //==============================================================================
  // Слон и комар
  void MultimethodElephantMosquito(Elephant* e, Mosquito* m) {
    printf("Слон - броня для комара. Не прокусить.\n");
  }
  // Собака и комар
  void MultimethodDogMosquito(Dog* d, Mosquito* m) {
    printf("У собаки не тот хвост, чтобы прихлопнуть комара.\n");
  }
  // Пингвин и комар
  void MultimethodPenguinMosquito(Penguin* p, Mosquito* m) {
    printf("А пингвины в Антарктиде. Комары далеко\n");
  }
  // Червяк и комар
  void MultimethodWormMosquito(Worm* w, Mosquito* m) {
    printf("Червяк живет глубоко, комарам до него далеко\n");
  }

  // Комар и слон
  void MultimethodMosquitoElephant(Mosquito* m, Elephant* e) {
    printf("Комар: Проблемы попить кровь у этих толстокожих слонов\n");
  }
  // Комар и собака
  void MultimethodMosquitoDog(Mosquito* m, Dog* d) {
    printf("Комар лезет собаке в нос.\n");
  }
  // Комар и пингвин
  void MultimethodMosquitoPenguin(Mosquito* m, Penguin* p) {
    printf("Комар решил слетать в Антарктиду в гости к Пингвину.\n");
  }
  // Комар и червяк
  void MultimethodMosquitoWorm(Mosquito* m, Worm* w) {
    printf("Комар вообще не знает, кто такой червяк.\n");
  }
  // Комар и комар
  void MultimethodMosquitoMosquito(Mosquito* m1, Mosquito* m2) {
    printf("Раззвенелись комары\n");
  }

Остается только добавить комара в тестовую функцию.


  // Обработка для комара
  Mosquito  m;
  printf("\n!!!!! Разнообразные манипуляции с комаром !!!!!\n");

  printf("\n==== Использование общей группы животных. Перемещение ====\n");
  printf("--- Прямое использование специализаций ---\n");
  struct AnyAnimal.mosquito am;   am.@ = &m;
  Moving<&am>();
  printf("--- Использование специализаций через обобщенный указатель ---\n");
  AnyAnimal* pam;
  pam =  &am;
  Moving<pam>();

  printf("\n==== Использование группы говорунов ====\n");
  printf("--- Прямое использование специализаций ---\n");
  struct SpeakingAnimal.mosquito  spm;   spm.@ = &m;
  Speaking<&spm>();
  printf("--- Использование специализаций через обобщенный указатель ---\n");
  speaker =  &spm;
  Speaking<speaker>();

  printf("\n==== Использование общей группы животных. Питание ====\n");
  printf("--- Прямое использование специализаций ---\n");
  Eating<&am>();
  printf("--- Использование специализаций через обобщенный указатель ---\n");
  Eating<pam>();

  printf("\n==== Использование группы летунов ====\n");
  printf("--- Прямое использование специализаций ---\n");
  struct FlyingAnimal.mosquito  fm;   fm.@ = &m;
  Flying<&fm>();
  printf("--- Использование специализаций через обобщенный указатель ---\n");
  FlyingAnimal* flyer =  &fm;
  Flying<flyer>();

  printf("
  \n==== Определение принадлежности животных к конкретной группе ====\n"
  );
  printf("--- Прямое использование специализаций ---\n");
  Group<&am>();
  printf("--- Использование специализаций через обобщенный указатель ---\n");
  Group<pam>();

  printf("\n==== Применение мультиметода для комара ====\n");
  printf("--- Прямое использование специализаций ---\n");
  Multimethod<&ae, &am>();
  Multimethod<&ad, &am>();
  Multimethod<&ap, &am>();
  Multimethod<&aw, &am>();

  Multimethod<&am, &ae>();
  Multimethod<&am, &ad>();
  Multimethod<&am, &ap>();
  Multimethod<&am, &aw>();
  Multimethod<&am, &am>();
  printf("--- Использование специализаций через обобщенный указатель ---\n");
  Multimethod<pae, pam>();
  Multimethod<pad, pam>();
  Multimethod<pap, pam>();
  Multimethod<paw, pam>();

  Multimethod<pam, pae>();
  Multimethod<pam, pad>();
  Multimethod<pam, pap>();
  Multimethod<pam, paw>();
  Multimethod<pam, pam>();
  ...

Что в сухом остатке?

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

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

KISS