Сквозная рубка в Рубине - PullRequest
4 голосов
/ 26 января 2010

Я пытаюсь добавить ведение журнала в метод извне (в аспектно-ориентированном стиле)

class A
  def test
    puts "I'm Doing something..."
  end
end

class A # with logging!
  alias_method :test_orig, :test
  def test
    puts "Log Message!"
    test_orig
  end
end

a = A.new
a.test

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

Ответы [ 2 ]

9 голосов
/ 26 января 2010

Другой альтернативой является использование несвязанных методов:

class A
  original_test = instance_method(:test)
  define_method(:test) do
    puts "Log Message!"
    original_test.bind(self).call
  end
end

class A
  original_test = instance_method(:test)
  counter = 0
  define_method(:test) do
    counter += 1
    puts "Counter = #{counter}"
    original_test.bind(self).call
  end
end

irb> A.new.test
Counter = 1
Log Message!
#=> #....
irb> A.new.test
Counter = 2
Log Message!
#=> #.....

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

class Module
  def add_logging(*method_names)
    method_names.each do |method_name|
      original_method = instance_method(method_name)
      define_method(method_name) do |*args,&blk|
        puts "logging #{method_name}"
        original_method.bind(self).call(*args,&blk)
      end
    end
  end
end

class A
  add_logging :test
end

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

class Module
  def self.define_aspect(aspect_name, &definition)
    define_method(:"add_#{aspect_name}") do |*method_names|
      method_names.each do |method_name|
        original_method = instance_method(method_name)
        define_method(method_name, &(definition[method_name, original_method]))
      end
    end
  end
  # make an add_logging method
  define_aspect :logging do |method_name, original_method|
    lambda do |*args, &blk|
      puts "Logging #{method_name}"
      original_method.bind(self).call(*args, &blk)
    end
  end
  # make an add_counting method
  global_counter = 0
  define_aspect :counting do |method_name, original_method|
     local_counter = 0
     lambda do |*args, &blk|
       global_counter += 1
       local_counter += 1
       puts "Counters: global@#{global_counter}, local@#{local_counter}"
       original_method.bind(self).call(*args, &blk)
     end
  end      
end

class A
  def test 
    puts "I'm Doing something..." 
  end
  def test1 
    puts "I'm Doing something once..." 
  end
  def test2
    puts "I'm Doing something twice..." 
    puts "I'm Doing something twice..." 
  end
  def test3
    puts "I'm Doing something thrice..." 
    puts "I'm Doing something thrice..." 
    puts "I'm Doing something thrice..." 
  end
  def other_tests
    puts "I'm Doing something else..." 
  end

  add_logging :test, :test2, :test3
  add_counting :other_tests, :test1, :test3
end
2 голосов
/ 26 января 2010

Первый выбор: подкласс вместо переопределения:

class AWithLogging < A\
  def test
    puts "Log Message!"
    super
  end
end

Второй вариант: назовите свои методы более тщательно:

class A # with logging!
  alias_method :test_without_logging, :test
  def test
    puts "Log Message!"
    test_without_logging
  end
end

Тогда другой аспект использует другое имя происхождения:

class A # with frobnication!
  alias_method :test_without_frobnication, :test
  def test
    Frobnitz.frobnicate(self)
    test_without_frobnication
  end
end
...