Лучший способ добавить методы в класс во время выполнения - PullRequest
3 голосов
/ 23 марта 2012

Я должен добавить методы к классу во время выполнения.

class ExtendableClass
end

Методы для добавления объявлены в независимых классах.

module ExtensionClassOne
  def method_one
  end
end

module ExtensionClassTwo
  def method_two
  end
end

Я ищу (элегантный) механизм для добавления всех методов класса расширения в ExtendableClass.

Подход 1

Я думаю, что в явно включены классы расширения , например:

ExtendableClass.send( :include, ExtensionClassOne )
ExtendableClass.send( :include, ExtensionClassTwo )

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

Подход 2

Итак, я искал автоматический способ include этого метода для моего ExtendableClass класса.

Я думаю, объявить конкретного предка для этого классов расширения :

class ExtensionClassOne < Extension
  def method_one
  end
end

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

Как только у меня есть этот список, я легко могу ExtendableClass.include весь список классов. Даже если мне придется вызывать приватный метод здесь.

Подход 3

Также наследуя от Extension класса и обнаруживает во время объявления, когда этот класс используется в качестве предка . То, как ActiveSupport.included метод работает, как привязка события . Затем сделайте include там.

Любое решение для реализации подход 2 или подход 3 ? Вы рекомендуете подход 1 ? Новые подходы?

Ответы [ 6 ]

1 голос
/ 24 марта 2012

@ fguillen, вы правы, что "явный путь - самый чистый подход". Так как это так, почему бы вам не использовать самый «явный» код, который только можно представить:

class Extendable
end

class Extendable
  def method_one
    puts "method one"
  end
end

class Extendable
  def method_two
    puts "method two"
  end
end

... Другими словами, если вы определяете модуль, который будет автоматически включен в класс, как только он будет определен, зачем вообще беспокоиться о модуле? Просто добавьте ваши методы «расширения» прямо в класс!

1 голос
/ 23 марта 2012

Редактировать : Учитывая ваш ответ на мой комментарий к вопросу, я полагаю, это не то, что вы хотели. Я не вижу проблем с вашим «подходом 1» в этом случае; это то, что я бы сделал. В качестве альтернативы, вместо использования send для обхода закрытого метода, просто заново откройте класс:

class ExtendableClass
  include ExtensionOne
end

Если я понимаю, что вы хотите, я бы сделал это:

module DelayedExtension
  def later_include( *modules )
    (@later_include||=[]).concat( modules )
  end
  def later_extend( *modules )
    (@later_extend||=[]).concat( modules )
  end
  def realize_extensions # better name needed
    include *@later_include unless !@later_include || @later_include.empty?
    extend  *@later_extend  unless !@later_extend  || @later_extend.empty?
  end
end

module ExtensionOne
end

module ExtensionTwo
  def self.included(klass)
    klass.extend ClassMethods
  end
  module ClassMethods
    def class_can_do_it!; end
  end
end

class ExtendableClass
  extend DelayedExtension
  later_include ExtensionOne, ExtensionTwo
end

original_methods = ExtendableClass.methods
p ExtendableClass.ancestors
#=> [ExtendableClass, Object, Kernel, BasicObject]

ExtendableClass.realize_extensions

p ExtendableClass.ancestors
#=> [ExtendableClass, ExtensionOne, ExtensionTwo, Object, Kernel, BasicObject]

p ExtendableClass.methods - original_methods
#=> [:class_can_do_it!]
1 голос
/ 23 марта 2012

Подход 4 заключается в определении макроса на уровне класса в Object

class Object
  def self.enable_extension
    include InstanceExtension
    extend ClassExtension
  end
end

и вызове этого макроса во всех ваших классах, которые вы хотите расширить.

class Bacon
  enable_extension
end

Car.enable_extension

Таким образом,

  • вам не нужно использовать #send для обхода инкапсуляции (подход 1)
  • вы можете наследовать от любого класса, который хотите, потому что все наследует от Object в любом случае (кроме 1.9BasicObject)
  • использование вашего расширения носит декларативный характер и не скрыто в некоторых хуках

Недостаток: вы можете встроить классы monkeypatch и можете разрушить мир.Выбирайте длинные и загадочные имена.

1 голос
/ 23 марта 2012

Метод included на самом деле является крючком. Он вызывается всякий раз, когда вы наследуетесь от:

module Extensions
  def someFunctionality()
    puts "Doing work..."
  end
end
class Foo
  def self.inherited(klass)
    klass.send(:include, Extensions) #Replace self with a different module if you want
  end
end
class Bar < Foo
end
Bar.new.someFunctionality                  #=> "Doing work..."

Существует также хук included, который вызывается, когда вы включены:

module Baz
  def self.included(klass)
    puts "Baz was included into #{klass}"
  end
end
class Bork
  include Baz
end

Выход:

Baz was included into Bork
0 голосов
/ 24 марта 2012

Очень хитрым решением, я думаю, слишком много чрезмерного проектирования, было бы взять унаследованный хук , который @ Linux_iOS.rb.cpp.c.lisp.m.sh прокомментировал, и сохранить все икаждый дочерний класс в Set и объединил его с предложением @Mikey Hogarth method_missing для поиска всех методов этого дочернего класса каждый раз, когда я вызываю метод в классе Extendable.Примерно так:

# code simplified and no tested
# extendable.rb
class Extendable
  @@delegators = []

  def self.inherited( klass )
    @@delegators << klass
  end

  def self.method_missing
    # ... searching in all @@delegators methods
  end
end

# extensions/extension_one.rb
class ExtensionOne < Extendable
  def method_one
  end
end

Но логика method_missingrespond_to?) будет очень сложной и грязной.

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

0 голосов
/ 24 марта 2012

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

# extendable.rb
class Extendable
  def self.plug( _module )
    include( _module )
  end
end

# extensions/extension_one.rb
module ExtensionOne
  def method_one
    puts "method one"
  end
end
Extendable.plug( ExtensionOne )

# extensions/extension_two.rb
module ExtensionTwo
  def method_two
    puts "method two"
  end
end
Extendable.plug( ExtensionTwo )

# result
Extendable.new.method_one # => "method one"
Extendable.new.method_two # => "method two"
...