А как вы создаете синглтоны в Ruby?

    Как создать синглтон в Ruby?
    Лично мне приходят на ум 4 способа.


    image


    Способ первый: include Singleton


    В стандартной библиотеке определен модуль Singleton, который производит
    некоторые действия над классом, в частности:


    • Делает .new приватным
    • Добавляет .instance, который создает и/или возвращает экземпляр
    • Переопределяет #dup и #clone, чтобы те вызывали ошибку

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


    Способ второй: модуль с module_function


    У Module есть метод #module_function, который позволяет использовать
    определяемые методы "на себе". Такой подход используется, например, в
    Math. Пример:


    module M
      module_function
    
      def f
        :f
      end
    end
    
    M.f # ==> :f

    Я бы не рекомендовал такую реализацию синглтона по нескольким причинам:


    • Это все же остается миксином, который можно включить в другой
      класс/модуль. Это, конечно, не страшно, но что-то здесь не так.
    • Сделать приватные методы можно только с помощью костылей, т.к. module_function
      создает публичную копию метода в самом себе. Я с ходу придумал только такой:


      module M
        module_function
      
        def f
          g
        end
      
        def g
          'hello'
        end
      
        singleton_class.send(:private, :g)
      end
      
      M.f # ==> 'hello'
      M.g # ==> NoMethodError

    • (Личная причина) Модули, использующие module_function по моему мнению должны
      быть сборником stateless методов, помогающих в чем-либо. Соответственно,
      include MyModule будет использоваться только для того, чтобы сделать методы
      доступными в текущем модуле без обращения к MyModule. Такой сценарий
      использования приводится в "The Ruby Programming Language" с Math

    Кстати, можно для этих же целей использовать extend self вместо
    module_function. Это избавит от проблемы приватных методов. Но, допустим,
    небезызвестный ruby-style-guide не одобряет такой подход (ссылка:
    https://github.com/bbatsov/ruby-style-guide#module-function)


    Думаю, очевидно, что extend self работает иначе, но я не уверен, что есть
    какая-то опасная разница.


    upd. все-таки не очень очевидно. extend self заставляет модуль добавить самого себя в список подключенных модулей (не знаю, как это проще написать), а module_function создает копии методов. Если конкретно, то посмотрите на код:


    module M
      module_function
      def f
        :f
      end
    end
    
    class A
      include M
    end
    
    M.f # ==> :f
    # module_function делает методы приватными в include
    A.new.send(:f) # ==> :f
    
    module M
      def f
        :TROLOLO
      end
    end
    
    A.new.send(:f) # ==> :TROLOLO
    M.f # ==> :f

    Способ третий: класс/модуль только с методами класса


    class MyClass
      def self.f
        :f
      end
    
      def self.g
        :g
      end
    end
    
    MyClass.f # ==> :f

    или так:


    class MyClass
      class << self
        def f
          :f
        end
    
        private
    
        def g
          :g
        end
      end
    end
    
    MyClass.f # ==> :f
    MyClass.g # ==> NoMethodError

    Разумеется, вместо class можно использовать module. В вышеупомянутом
    стайл-гайде такой подход не рекомендуется. Вместо него рекомендуют
    module_function.


    В моей практике такой подход встречался чаще всего. Лично мне он всегда казался
    каким-то страшным костылем, но при этом он мне нравится больше использования
    Singleton, т.к. MySingleton.do_something для меня выглядит привлекательнее
    MySingleton.instance.do_something.


    Создать экземпляр Object


    В последнее время я постоянно использую такой подход:


    MySingleton = Object.new
    class << MySingleton
      def f
        g
      end
    
      private
    
      def g
        puts 'hello'
      end
    end

    Теперь наш синглтон — это просто экземпляр Object с нужными нам методами:


    MySingleton.class # ==> Object

    Вот только и здесь есть проблемы:


    • Мы можем использовать #clone/#dup. Решение: переопределить их, как это
      сделано в Singleton
    • При инспектировании объекта мы получаем что-то вроде #<Object: ...>. Решение: переопределить методы #to_s и #inspect. Кстати,
      ruby-style-guide рекомендует делать это на всех "собственных" (локальных? Не
      могу подобрать слово) классах. Ссылка:
      https://github.com/bbatsov/ruby-style-guide#define-to-s
    • пишут, что у такого подхода есть проблемы с генерацией документации. Не
      могу подтвердить или опровергнуть, т.к. не использую генераторы. Ссылка:
      https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along

    Небольшое отступление: class << self


    Думаю, все видели синтаксис:


    class MyClass
      class << self
        def do_something_on_class
          ...
        end
      end
    
      def do_something_on_instance
        ...
      end
    end

    При этом я неоднократно замечал, что человек не знает, что означает эта
    конструкция. Собственно, в Ruby у объектов на самом деле есть два класса: тот,
    чьим экземпляром он является, и т.н. "singleton class" — класс синглтона.


    Наверняка вы видели примеры, где мы определяем методы прямо на объектах.
    Как-то так:


    x = Object.new
    def x.hey
      'hey'
    end
    
    x.hey # ==> 'hey'

    В класс-ориентированном ООП у объекта нет своих методов. Поведение объекта
    определяется классом, которому он принадлежит. Поэтому мы не можем определить
    метод на объекте с помощью def x.hey, мы должны определить его в классе. Вот
    только если мы сделаем это, то тогда все экземпляры Object должны будут
    получить метод #hey, чего мы не хотим. Поэтому Ruby создает "дополнительный"
    класс у объекта, называемый класс синглтона. Получить его можно с помощью метода
    #singleton_class. В общем, я увлекся и, наверное, только запутал тех, кто не
    знал о "singleton class". Это очень интересная сторона Ruby, поэтому предлагаю
    прочитать о ней самостоятельно.


    Собственно, если коротко, то конструкция class << some_object "входит" в класс
    сингтона. Сравните:


    class A # enter class A scope
      def hey
        'hey'
      end
    end
    
    class << A # enter class A singleton class scope
      def hey
        'hello'
      end
    end
    
    A.new.hey # ==> 'hey'
    A.hey     # ==> 'hello'
    Ну так… Как?

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

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 11
    • 0
      Во всяком случае, я бы писал
      что-то подобное, будь я Java-программистом.

      Будь Вы Java-программистом, то использовали бы shared instance в DI контейнере, а не сомнительные паттерны.

      • 0

        Есть паттерны, которые не имеют смысла в динамических языках. Singleton — один из них.

        • 0
          Ты даешь им объект класса, а они делают include Singleton.
          Что дальше? аннотации? или… может… СТАТИЧЕСКАЯ ТИПИЗАЦИЯ?
          Кошмар рубиста наяву.

          Спасибо, поплакал :)
          • 0

            Тоже крайне удивлен результатами опроса. Может быть, это джависты портят статистику? Хммм....

            • 0

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


              Пытаться дословно реализовать все паттерны из книги во всех языках — это бессмысленный карго-культ. Я вам выше написал, что Singleton не имеет никакого практического и теоретического смысла в Ruby. Все это многократно уже обсуждалось, на hacker news, например, можно найти статьи об этом.

              • 0

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


                Мне вот, допустим, не нравится видеть new каждый раз, когда я хочу воспользоваться чем-то вроде SomeExternalApi:


                SomeApi.new.do_something
                # vs
                SomeApi.do_something

                Синглтон — это не паттерн N из книги банды четырех. Это просто идея (впрочем, как и большинство паттернов). Далеко не всегда они решают какую-то архитектурную задачу, зачастую они просто позволяют сделать код красивее (понятнее).


                Предвижу минусы, но я настаиваю на этой точке зрения.

                • 0

                  К сожалению, плохо знаком с Ruby. В Python это делается при помощи статических методов или методов класса. Или при помощи паттерна Borg. Думаю, что и в Ruby должно быть что-то похожее.


                  А вообще, мне кажется что тащить в другой язык инородные приемы и парадигмы — напрасная трата сил. Я как раз сейчас работаю с относительно большим проектом, который написал человек как первый на Python после Java. Это просто нечитаемый ужас-ужас-ужас. В Риме надо поступать как римляне. Если хочется писать на Java, то не надо превращать в Java Python или Ruby. Можно просто писать на Java.

                  • 0
                    статических методов или методов класса

                    Этот способ указан в посте. С парой причин, почему он лично мне не нравится.


                    тащить в другой язык инородные приемы и парадигмы — напрасная трата сил

                    В руби шикарно уживаются приемы из ФП. Более того, о каких приемах в контексте данного поста идет речь?
                    Синглтон — всего лишь название, чтобы всем было понятно, что речь идет о единственном представителе своего класса.


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

                    • 0

                      Singleton — это просто глобальная переменная. Я вот ни разу в жизни еще не сталкивался с ситуацией, где он реально требуется. Мне кажется, что это скорее антипаттерн, а то, с какими целями вы его пытаетесь пересоздать — двойной антипаттерн.

                      • 0

                        Я вам привел две строчки для сравнения:)
                        Это просто делает код красивее.


                        Что является антипаттерном? Приемы, которые нанесут вред коду? Какой вред вы видете в глобальной константе, содержащей stateless-объект с нужными методами?


                        Ну и еще можно, конечно, говорить о том, что это избавляет от множеств созданий/уничтожений объекта и т.д. и т.п., но это роли не играет, разумеется.


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


                        UPD. Вы упомянули статические методы. Класс в данном случае ведь тоже объект. Просто в питоне и джаве это не подтверждается на уровне языка. Я могу ошибаться, но в ООП нет "статических методов". Есть только объекты и их методы. Соответственно, говоря о статических методах, мы начинаем интерпретировать класс как самостоятельный объект (в Ruby так оно и есть)

                        • 0

                          Уточнил. Я не прав насчет питона. В питоне классы — тоже объекты, что просто замечательно

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