вторник, 14 мая 2013 г.

Класс — это модуль и не модуль, и что с этим делать

Есть в Ruby одно заметное нарушение LSP: класс, будучи вообще-то модулем (Class — прямой наследник Module), не может быть использован вместо модуля в качестве примеси, т.е. в “include” или “extend”. Это, в принципе, обоснованно, поскольку они не только объекты, но и конструкции языка...

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

module Alpha

  def alpha
  end

  class << self

    def alpha2
    end

  end

end

class Beta

  include Alpha

end

Так вот, метода Beta.alpha2 мы таким образом не получим, хотя если бы Alpha был классом и использовалось бы наследование, метод класса унаследовался бы ничуть не хуже метода экземпляра. Так и тянет выполнить что-нибудь вроде “Beta.extend(Alpha.singleton_class)”, но нельзя.

Решение, безусловно, достаточно очевидное — вынести соответствующий функционал в отдельный модуль и подмешать его как к классу, так и к основному модулю (если нужно). А еще лучше — подмешивать автоматически при включении основного модуля. Т.е. получается следующее:

module Alpha

  module Cls

    def alpha
    end

  end

  extend Cls

  class << self

    def included target
      target.extend Alpha::Cls
    end

    private :included

  end

end

class Beta

  include Alpha

end

Повторюсь: решение очевидное, и, возможно, не стоило б о нем писать, если б не одно «но» — очевидно оно именно при такой постановке задачи, тогда как в реальности до этого еще дойти надо. Что сделать гораздо проще, если помнить о существовании такого паттерна.

PS. Кстати, в процессе обдумывания обнаружено еще одно нарушение LSP: класс Class не совсем обычный — от него нельзя создать наследника (от Module можно).

Комментариев нет:

Отправить комментарий