Включите модуль в экземпляры вместо класса - PullRequest
2 голосов
/ 18 марта 2019

У меня есть модуль:

module MM::NN
  def test_method
    puts "module method called"
  end
end

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

class A
  def initialize(add_module)
    self.class.send(:include, MM::NN) if add_module
  end
end

Я ожидал:

A.new(true).test_method  # >> module method called
A.new(false).test_method  # >> NoMethodError

Но test_method определен во всех случаях. Вызов класса с аргументом true добавляет модуль к экземплярам, ​​созданным позже. Я получил:

A.new(true).test_method  # >> module method called
A.new(false).test_method  # >> NoMethodError

Инициализация класса с аргументом false при первом вызове инициализатора и true во втором даст желаемый результат, так как метод test_method добавляется позже:

A.new(false).test_method  # >> NoMethodError
A.new(true).test_method  # >> module method called

потому что модуль привязан к самому классу.

Как сделать метод доступным в определенных случаях, как описано выше?

Как мне решить другой случай:
где class A наследуют свойства и методы другого class B, а class B также содержит test_method. Как можно использовать модуль TestModule s test_method вместо метода внутри class B.

И каковы различия (или) побочные эффекты при вызове методов при включении модуля в класс: 1) включить модуль за пределами инициализации.
2) Включите модуль внутри инициализации.

Ответы [ 4 ]

6 голосов
/ 18 марта 2019

Когда вы вызываете self.class.send(:include, MM::NN), это делает необратимое изменение класса (план для всех экземпляров).

Если вы хотите, чтобы модуль был включен только для экземпляра, вы должны использовать одноэлементный класс ( Что такое одноэлементный класс в ruby? ):

module TestModule
  def test_method; "ok"; end
end

class A
    def initialize(add_module)
        singleton_class.include(TestModule) if add_module
    end
end

A.new(true).test_method  # module method called
A.new(false).test_method # NoMethodError

Обратите внимание, что я изменил display на test_method, потому что display это встроенный метод ядра, поэтому он не вызовет NoMethodError, даже если вы не включите модуль в экземпляр.

Обратите внимание, что ответ Озера такой же, но с другим синтаксисом.

- редактировать в ответ на комментарий -

Добавление методов в класс singleton имеет приоритет над методами, добавленными в обычный класс, и если вы включаете несколько модулей в класс singletonпоследний из них будет иметь приоритет:

module A; def fn; 1; end; end
module B; def fn; 2; end; end
module C; def fn; 3; end; end

class D
  include A
  def initialize(add_b, add_c)
    singleton_class.include(B) if add_b
    singleton_class.include(C) if add_c
  end
end

puts D.new(false, false).fn # => 1
puts D.new(true, true).fn   # => 3
4 голосов
/ 18 марта 2019

include добавляет все методы из модуля во все экземпляры класса, в который входит модуль. Чтобы добавить методы к отдельному объекту, вы можете использовать extend:

class A
  def initialize(add_module)
    extend(MM::NN) if add_module
  end
end
2 голосов
/ 18 марта 2019

Это добавит его только к инициализируемому экземпляру:

class A
  def initialize(add_module)
    if add_module
      (class <<self; include MM::NN; end)
    end
  end
end
1 голос
/ 18 марта 2019

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

Вместо этого я предлагаю вам попытаться выполнить то, что выделают использование композиции вместо наследования.То есть внутри class A имеется переменная экземпляра, которая содержит поведение в MM:NN.Это зависит от специфики поведения в этом модуле, но может выглядеть примерно так:

# This class is here because I'm assuming you can't make `MM::NN` a class in its own right. However it probably needs additional data or methods to actually work with the MM::NN methods
class NN_implementation
  include MM::NN
end

class A
  def initialize(use_module)
    # if this class needs access to data or methods from A, you could add `self` as an input arg to the constructor of `NN_implementation`
    @nn = NN_implmenetation.new if use_module
  end

  def has_nn?
    !@nn.blank?
  end
end

Тогда вы можете (1) предоставить конкретные NN методы, которые вы хотите использовать в качестве методов в классе A или вы можете открыть экземпляр nn экземпляра, используя attr_reader, и вызвать что-то вроде my_a_instnace.nn.some_nn_method if my_a_instance.has_nn?.Опять же, в значительной степени зависит от специфики A и MM::NN, как я в конечном итоге закодирую это для готового к использованию кода, но что-то в этом духе делает определения классов чистыми и не допускает путаницы, откуда исходит конкретное поведение.

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