Использование подобия паттерна наблюдатель на С

    Недавно вспоминал о том как читал книгу по паттернам проектирования для java и из-за того что я всё также не пойму как это делается по памяти в ооп, решил написать свою реализацию на си.

    Я решил посмотреть ещё раз в этой книге по java как реализуется этот паттерн. Полистал всю главу, посмотрел код, но в голове ничего не отложилось. Почему так? ) Правильно ли мыслить в стиле ооп? Например я знаю немного java и c++, но насколько удобно можно сделать реализацию так, чтобы ей было приятно пользоваться и не забывалось? Также для эксперимента я посмотрел как это делается на c++. Ну честное слово, на такой код мне сложно смотреть, надо вчитываться в каждую строку и думать.

    Так что же я сделал, что мне может пригодиться в будущем в программировании? Встречайте, реализация на github. Хочу объяснить простоту использования того что я написал. В этой реализации не нужно создавать структуру, которая будет хранить данные. Сама же основная структура находится за кулисами. Например я хочу подписаться в одном файле на издателя TRADE. Этот издатель например будет мне предоставлять скидки по товару. Я рассматриваю наблюдателя как обычную функцию и объявляю её согласно правилу.

    void sub_trade ( void *event, void *data ) {
      /* здесь выводиться к примеру новые скидки на экран. */
    }
    

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

    enum { TRADE };
    init_publisher ( TRADE, sub_trade, NULL );
    

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

    Я объявляю ещё функцию подписчика.

    enum { TRADE };
    void notify_chat_trade ( void *event, void *data ) {
      /* здесь я рассылаю сообщения в чат */
    }
    ...
    init_publisher ( TRADE, notify_chat_trade, NULL );
    

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

    /* для простоты я передал число 10, но конечно нужно отправлять специальные данные. */
    send_event ( TRADE, 10 );
    

    Вот так вот удобно и просто без сложностей использования можно получить нужный результат. Я думаю у меня хорошо получилось.

    Кстати, кто может мне объяснить, это структурное программирование или процедурное?

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 30

      +1

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

        0
        Хорошо. Понятно. Спасибо за разъяснения.
        +4

        Хотел поприветствовать Вас на Хабре, но был очень удивлен, что это не первый пост…
        По существу:


        1. Редко ценятся статьи формата "Я не разобрался в этих ваших X, так что сделал Y"
        2. Все же стоило разобраться, как наблюдатель реализуется в Java, ибо в базовой версии это крайне простой код — просто сохраняется список слушателей событий (список ссылок). В C# существует некоторый синтаксической сахар этого паттерна в виде event с возможностью подписки делегатов (умных указателей на функцию).
        3. К сожалению, из статьи непонятно, как именно Вы реализовали паттерн в Си. Вариантов могло быть как минимум несколько, и читатели могут только предполагать. В статье описан скорее интерфейс пользователя, но опять же реализация не выглядит библиотечной.
        4. В целом полезность статьи ставится под вопрос, ибо, как известно, на С можно реализовать практически все, даже ООП, которого Вы в статье "побаиваетесь". С другой стороны, гораздо более распространенным в С является механизм callback'ов, который так же решает вопрос уведомления о событиях (и др.)
          0
          Спасибо за комментарий. Я в основном программирую на си. И то редко. ООП почти не затрагиваю. Да библиотечной наверное не выглядит, но она для встраивания в проект. Ведь если приложения будут использовать одну и ту же библиотеку, то смогут пользоваться одинаковыми данными?
          0
          Я думаю у меня хорошо получилось.

          Безотносительно паттернов, у вас получилось плохо: у вас нигде нет никакого контроля за тем, какие данные высылает издатель и получает подписчик. Ничто не мешает послать строчку, а в подписчике обработать ее как число со всеми вытекающими интересными последствиями. Или, из той же оперы, как (в большой программе) гарантировать, что все используют один и тот же enum для событий? А что будет, если использовать два?

            0
            А что будет, если использовать два?

            Как это два? Можно создать сколько угодно издателей. Из любой точки программы можно использовать одинаковый enum. Правильность написания кода ложиться на программиста, он же пишет программу, а не прикалывается.
            у вас нигде нет никакого контроля за тем, какие данные высылает издатель и получает подписчик.

            Зато можно выслать абсолютно любые данные. В ооп вы бы указывали какие данные получать, а мне нужна была универсальность, которая подходит к любой ситуации без переписывания кода. То есть программист сам следит за передаваемыми данными. Вам ведь никто не сможет испаганить код, кроме разработчиков.
              +2
              Как это два?

              Вот так:


              enum { TRADE };
              init_publisher ( TRADE, sub_trade, NULL );
              enum { SELL };
              init_publisher ( SELL, sub_sell, NULL );

              Из любой точки программы можно использовать одинаковый enum.

              А можно и не использовать. И что получится?


              Правильность написания кода ложиться на программиста, он же пишет программу

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


              Зато можно выслать абсолютно любые данные.

              А зачем это нужно?


              В ооп вы бы указывали какие данные получать

              Это неправда, кстати. ООП не имеет к этому никакого отношения.


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

              Правда?


              И как же вы без переписывания кода подойдете к ситуации, когда отправитель стал слать строки, когда получатель ожидает числа?


              Вам ведь никто не сможет испаганить код, кроме разработчиков.

              Ну так проблема-то как раз в разработчиках.

                –4
                Ну так проблема-то как раз в разработчиках.

                Ну судя по вашему коду, который вы в пример написали с двумя enum, как можно так писать? Заводите отдельный хедер файл с enum, который будет доступен нужным функциям. И используете. Если вы не знаете как правильно писать код, то так и будете ошибаться. Еще насчет enum. А что вы хотите, чтобы названия издателя были как строки, чтобы программа на строки сравнивала типа такого?
                init_publisher ( "trade", sub_trade, NULL );
                init_publisher ( "sell", sub_sell, NULL );
                

                Ну так я скажу что так дольше сравнивать. Гораздо удобней завести один enum. О, можно ещё бинарный поиск приделать к enum, чтобы побыстрее находило нужного издателя.

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

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

                Как это не имеет, а как вы тогда будете нужные типы данных рассылать подписчикам?
                  +3
                  Ну судя по вашему коду, который вы в пример написали с двумя enum, как можно так писать?

                  А что мешает?


                  Заводите отдельный хедер файл с enum, который будет доступен нужным функциям.

                  И как гарантировать, что функции используют один и тот же enum?


                  А что вы хотите, чтобы названия издателя были как строки, чтобы программа на строки сравнивала типа такого?

                  Нет, я хочу, чтобы события были гарантированно уникальными внутри пространства имен.


                  Гораздо удобней завести один enum.

                  Завести — удобнее, а пользоваться — нет.


                  О, можно ещё бинарный поиск приделать к enum

                  Про хэш-таблицы вы, я так понимаю, не слышали?


                  Как можно ошибиться?

                  Легко. Вы правда не понимаете, как можно не те данные отправить?


                  Вы передаете число, какая тут проверка блин.

                  Простая: на тип. Вы правда не слышали про типизацию, или просто не понимаете, как ее здесь применить?


                  Вы что, не знаете что вы делаете?

                  Я как раз знаю, что я делаю. И именно поэтому я предпочту, чтобы все, что может проверить компьютер, проверял компьютер.


                  Как это не имеет, а как вы тогда будете нужные типы данных рассылать подписчикам?

                  Очень просто.


                  let subscribe<event> (handler : (event -> unit)) = ...
                  let emit event = ...
                  
                  let sellSubcriber1 (event : sell) = ...
                  let sellSubscriber2 (event : sell) = ...
                  let buySubscriber (event : buy) = ...
                  
                  subscribe(sellSubscriber1) //подписывается на sell
                  subscribe(sellSubscriber2) //подписывается на sell
                  subscribe(buySubscriber) //подписывается на buy
                  
                  //две следующих строчки дадут ошибку компиляции
                  //subscribe<sell>(buySubscriber)
                  //subscribe<buy>(sellSubscriber1)
                  
                  emit(sell(5)) //вызовет sellSubscriber1 и sellSubscriber2
                  emit(buy(3)) //вызовет buySubscriber
                  

                  Никакого ООП. Ну то есть вообще ровно ноль. И нет ни одной из описанных мной в вашем коде проблем.


                  (Ну да, это не C)

                    –5
                    ну а event: sell тогда что значит? Это тип? Ну вот видите, ваш код не универсальный. Вы создаете функции с разными event. В моем случае остается только пользоваться готовым решением ( не нужно что-то в коде менять, потому что можно передавать любые данные ). А в вашем подготавливать функции с нужными типами и по сигналу передавать.

                    Я как раз знаю, что я делаю. И именно поэтому я предпочту, чтобы все, что может проверить компьютер, проверял компьютер.

                    Ну пусть у вас будет так. А у меня если появиться проблема, то я проверю все ли правильно написал. Нет ли логической ошибки.

                    Завести — удобнее, а пользоваться — нет.

                    Как знаете. Вам не удобно пользоваться. Мне удобно. Почему я должен знать что именно вам не удобно пользоваться?

                    И как гарантировать, что функции используют один и тот же enum?

                    Ну что вы все на компилятор полагаетесь. Смотришь в хедер какой использовать enum и пишешь в функцию. Или у вас не так все происходит. Вы наверное по памяти пишете enum. Наверное еще используете ide, которая вам всячески подсказывает, но вы все равно допускаете ошибки. Единественное что вас спасает, так это что компьютер проверяет вас, что вы не накасячели нигде.
                    let subscribe<event> (handler : (event -> unit))

                    А что шаблон это не ооп? Вы всего лишь показываете готовые возможности языка для использования издателя и подписчика. В си стандартных средств нет. Нужно писать свою реализацию. Ну вот я и написал.
                      +1
                      ну а event: sell тогда что значит? Это тип?

                      Да, это тип.


                      Ну вот видите, ваш код не универсальный.

                      Отнюдь. Код, реализующий pub-sub — универсальный, он работает с любым типом.


                      Подписчики, конечно, не универсальные, но так и должно быть. В них прикладная логика.


                      А у меня если появиться проблема

                      Разница в том, что я не получу этого "если". На одну (или несколько) проблем меньше в продакшне.


                      Ну что вы все на компилятор полагаетесь.

                      Потому что он ошибается меньше людей.


                      Смотришь в хедер какой использовать enum и пишешь в функцию.

                      А зачем это делать, если этого можно избежать?


                      А что шаблон это не ооп?

                      Нет, конечно. И это не шаблон, кстати.


                      Вы всего лишь показываете готовые возможности языка для использования издателя и подписчика.

                      Тоже нет. Я использую самые обычные функции. Вам полную реализацию показать, что ли?

                        –1
                        А почему вы мне показываете как это делается на js? Почему вы сишника учите программировать, если сами показываете код на js. Если хотите показать как надо правильно делать, то пишите на си. То же мне, показывает как это удобно делается на js. Как будто статья про js.
                          0
                          А почему вы мне показываете как это делается на js?

                          Это не js. В js, если что, вообще динамическая типизация (и другой синтаксис, кстати).


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


                          Если хотите показать как надо правильно делать, то пишите на си.

                          Зачем? Си я знаю недостаточно хорошо, будут глупые ошибки, а основной смысл подхода никуда не денется.

                            –1
                            Зачем? Си я знаю недостаточно хорошо, будут глупые ошибки, а основной смысл подхода никуда не денется.

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

                              Я указываю на недостатки вашего решения, а не языка.


                              Чтобы мне указывать как надо на си писать, у вас должен быть уровень повыше моего.

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


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

                                –2
                                Я указываю на недостатки вашего решения, а не языка.

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

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

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

                                  Начнем с того, что ваше решение — не от той задачи. Вы реализовали pub/sub, а не observer.


                                  я уже писал тестовую программу. все работает без проблем.

                                  У вас? Возможно. Никто не говорит, что для вас это решение не подходит.


                                  Оно не подходит для использования в проектах, которые поддерживает больше одного человека.


                                  (ну то есть я бы вообще сказал, что оно не подходит для продакшна, но я какого только в продакшне кода не видел)


                                  ошибиться может только тот, который в си не разбирается.

                                  Как ни странно, люди часто ошибаются, даже зная язык. От усталости, от невнимательности. Это рутина. Люди ошибаются.


                                  у вас уровень меньше моего.

                                  Я бы на вашем месте не стал так говорить.


                                  вы указали на такие ошибки, которые может совершить только человек, который не программирует вообще.

                                  Вы, вероятно, не представляете себе, какие (нелепые) ошибки совершают люди, которые умеют программировать, и делают это годами за деньги.


                                  в моем случае программист должен задаться вопросом, а где брать издателей

                                  Нет, он должен задаться вопросом, какие события существуют (либо — какие топики, но это не ваш вариант).


                                  что у него в голове твориться я не знаю

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

                                    0
                                    Начнем с того, что ваше решение — не от той задачи. Вы реализовали pub/sub, а не observer.

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

                                      +1
                                      Имеется ввиду что это выполняет ту же работу, только на си.

                                      Нет, не ту же.


                                      Я думал что это подобие паттерна наблюдатель, где есть издатель и подписчик.

                                      Издатель (publisher) и подписчик (subscriber) есть в паттерне publisher-subscriber. В паттерне наблюдатель (observer) есть субъект и наблюдатели.


                                      А ведь действительно есть издатель и подписчик.

                                      … что и показывает, что это — pub/sub, а не observer.


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

                                      Да, конечно. Я вообще не очень понимаю, зачем файлами мыслить.

                                        0
                                        Да, конечно. Я вообще не очень понимаю, зачем файлами мыслить.

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

                                          Ну да, это тривиальная повсеместная задача. Я же говорю: да, конечно можно.


                                          (Впрочем, зачем мыслить файлами, все равно не понимаю. И не люблю языки, в которых файлы автоматически задают структуру.)

                                        0

                                        Собственно, чтобы вы понимали, "подобие паттерна наблюдатель" без ООП делается вот так (на любом языке, которые умеет ссылки на функции):


                                        subject(subject_args, ..., observer[] observers)

                                        Проще говоря, через колбеки, как вам уже и написали.

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

                                            Здесь нет ни одного издателя, это не pub/sub. Надо вам несколько субъектов? Пожалуйста:


                                            subject1(..., observer1[] observers)
                                            subject2(..., observer2[] observers)
                                            subject3(..., observer1[] observers)

                                            Это же просто функции, их может быть сколько угодно.

                                              0
                                              Я так понимаю что вам придется писать одну и ту же реализацию для каждого subject1,2,3. а нет универсального способа. чтобы вы например написали один раз реализацию и пользоваться ей, не изменяя и не добавляю каждый раз код для создания нового издателя?
                                                0
                                                Я так понимаю что вам придется писать одну и ту же реализацию для каждого subject1,2,3.

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

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

                                                    У меня нет издателей. У меня субъекты.


                                                    Или что там может быть в издателе такого, что приходится переписывать каждый раз заново издателя?

                                                    Прикладная задача, которую он решает.


                                                    Не знаю почему вам этот способ кажется правильней.

                                                    Потому что (а) так написано в GoF, и (б) это полностью совпадает с моим опытом.


                                                    Для меня удобно сделать один раз реализацию и пользоваться ей.

                                                    Одну реализацию чего?


                                                    Смотрите, вот первый субъект, функция, которая скачивает файлы и рапортует в качесте состояния проценты:


                                                    delegate void ProgressObserver(float progress);
                                                    
                                                    byte[] Download(string path, ProgressObserver[] observers)
                                                    {
                                                      FileStream stream = GetStream(path);
                                                      byte[] result = new byte[stream.Length];
                                                      int offset = 0;
                                                      while(!stream.EOF)
                                                      {
                                                        byte[] buffer = stream.ReadBytes(BufferSize);
                                                        buffer.CopyTo(result, offset);
                                                        i += buffer.Length;
                                                        foreach (ProgressObserver observer in observers)
                                                          observer((float)i/result.Length);
                                                      }
                                                      return result;
                                                    }

                                                    А вот второй субъект, функция, которая слушает клавиатуру, и рассылает нажатые клавиши


                                                    delegate void KeyObserver(int keyCode);
                                                    
                                                    void Listen(KeyObserver[] observer)
                                                    {
                                                        while(true)
                                                        {
                                                          int keyCode = Console.ReadKey();
                                                          foreach (KeyObserver observer in observers)
                                                            observer(keyCode);
                                                        }
                                                    }

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

                      +2
                      Если вы один работаете над проектом, тогда за свой код будете только вы нести ответственность. А если вы работаете в коллективе, то ваши функции и подпрограммы доступны другим программистам. enum {} по сути является типом int, а значения TRADE и SELL эквивалентны, хотя принадлежат разным перечислениям. Язык С допускает смешивание этих типов. Поэтому ваш «паттерн» ненадёжен для языка С и даже проверка аргументов не спасёт. Либо использовать С++ со строгой типизацией, либо менять свой стиль/паттерн С.
                0

                1) хедер файл с enum ужасный подход. Любое изменение в нем форсит перекомпилирование всех файлов, которые его подключают. Если для вас это мелочь, то вы ни разу не работали с большим проектом
                2) уход от типов ни к чему хорошему не приводит. Вам кажется, что все очевидно, а по факту у вас может быть N коллбеков принимающих разные типы данных и K вызовов с разными типами данных. В итоге сложность понимания такого кода растёт квадратично
                3) если вас так беспокоит дублирование кода, то воспользуйтесь макросами. А еще лучше все же откройте Александреску и прочитайте, как можно реализовать visitor в плюсах в общем виде, типизированно и эффективно
                4) не надо отожествлять синтаксис какого-то языка или саму систему типов с ООП. Предлагаю для начала открыть книжку или на крайний случай википедию и прочитать, что же значит этот термин.

                Only users with full accounts can post comments. Log in, please.