Декораторы в Ruby (мигрирующий из Python)

XSLT является трудным работать с, но как только Вы завоевываете его, у Вас будет очень полное понимание DOM и схемы. Если Вы также XPath, то Вы на Вашем пути к изучению функционального программирования и это подвергнет новым методам и путям о решении проблем. В некоторых случаях последовательное преобразование более мощно, чем процедурные решения.

28
задан jameshfisher 11 December 2009 в 22:58
поделиться

8 ответов

Вот еще один подход, который устраняет проблему с конфликтами между именами методов с псевдонимами (ПРИМЕЧАНИЕ, мое другое решение, использующее модули для украшения, также является хорошей альтернативой, поскольку оно также позволяет избежать конфликтов):

module Documenter
    def document(func_name)   
        old_method = instance_method(func_name) 

        define_method(func_name) do |*args|   
            puts "about to call #{func_name}(#{args.join(', ')})"  
            old_method.bind(self).call(*args)  
        end
    end
end

Вышеупомянутое код работает, потому что локальная переменная old_method поддерживается в новом методе 'hello', поскольку блок define_method является закрытием.

13
ответ дан 28 November 2019 в 03:24
поделиться

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

Декорирование методов экземпляра

#! /usr/bin/env ruby

# First, understand that decoration is not 'built in'.  You have to make
# your class aware of the concept of decoration.  Let's make a module for this.
module Documenter
  def document(func_name)   # This is the function that will DO the decoration: given a function, it'll extend it to have 'documentation' functionality.
    new_name_for_old_function = "#{func_name}_old".to_sym   # We extend the old function by 'replacing' it - but to do that, we need to preserve the old one so we can still call it from the snazzy new function.
    alias_method(new_name_for_old_function, func_name)  # This function, alias_method(), does what it says on the tin - allows us to call either function name to do the same thing.  So now we have TWO references to the OLD crappy function.  Note that alias_method is NOT a built-in function, but is a method of Class - that's one reason we're doing this from a module.
    define_method(func_name) do |*args|   # Here we're writing a new method with the name func_name.  Yes, that means we're REPLACING the old method.
      puts "about to call #{func_name}(#{args.join(', ')})"  # ... do whatever extended functionality you want here ...
      send(new_name_for_old_function, *args)  # This is the same as `self.send`.  `self` here is an instance of your extended class.  As we had TWO references to the original method, we still have one left over, so we can call it here.
      end
    end
  end

class Squarer   # Drop any idea of doing things outside of classes.  Your method to decorate has to be in a class/instance rather than floating globally, because the afore-used functions alias_method and define_method are not global.
  extend Documenter   # We have to give our class the ability to document its functions.  Note we EXTEND, not INCLUDE - this gives Squarer, which is an INSTANCE of Class, the class method document() - we would use `include` if we wanted to give INSTANCES of Squarer the method `document`.  <http://blog.jayfields.com/2006/05/ruby-extend-and-include.html>
  def square(x) # Define our crappy undocumented function.
    puts x**2
    end
  document(:square)  # this is the same as `self.document`.  `self` here is the CLASS.  Because we EXTENDED it, we have access to `document` from the class rather than an instance.  `square()` is now jazzed up for every instance of Squarer.

  def cube(x) # Yes, the Squarer class has got a bit to big for its boots
    puts x**3
    end
  document(:cube)
  end

# Now you can play with squarers all day long, blissfully unaware of its ability to `document` itself.
squarer = Squarer.new
squarer.square(5)
squarer.cube(5)

Все еще запутались? Я бы не удивился; это заняло у меня почти целый ДЕНЬ. Еще кое-что, что вам следует знать:

  • Первое, что является ВАЖНЫМ, это прочитать это: http://www.softiesonrails.com/2007/8/15/ruby-101-methods-and- сообщения . Когда вы вызываете 'foo' в Ruby, на самом деле вы отправляете сообщение его владельцу: «пожалуйста, вызовите свой метод 'foo'». Вы просто не можете напрямую удерживать функции в Ruby, как в Python; они скользкие и неуловимые. Вы можете видеть их только как тени на стене пещеры; вы можете ссылаться на них только через строки / символы, которые являются их именами. Попробуйте представить себе каждый вызов метода 'object.foo (args)', который вы делаете в Ruby, как эквивалент этого в Python: 'object. getattribute (' foo ') (args)'.
  • Прекратите писать какие-либо определения функций / методов за пределами модулей / классов.
  • С самого начала примите тот факт, что этот опыт обучения будет плавить мозг, и не торопитесь. Если Ruby не имеет смысла, пробейте стену, слейте чашку кофе или поспите ночью.

Украшение методов класса

Приведенный выше код украшает методы экземпляра. Что, если вы хотите украсить методы прямо в классе? Если вы читаете http://www.rubyfleebie.com/understanding-class-methods-in-ruby , вы обнаружите, что существует три метода для создания методов класса, но здесь работает только один из них.

Это анонимный метод class << self . Давайте сделаем то же самое, но так, чтобы мы могли вызывать square () и cube (), не создавая их экземпляров:

class Squarer

  class << self # class methods go in here
    extend Documenter

    def square(x)
      puts x**2
      end
    document(:square)

    def cube(x)
      puts x**3
      end
    document(:cube)
    end
  end

Squarer.square(5)
Squarer.cube(5)

Удачи!

11
ответ дан 28 November 2019 в 03:24
поделиться

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

Сказав все это, вот отличный код, который вы можете использовать.

def with_document func_name, *args
  puts "about to call #{func_name}(#{args.to_s[1...-1]})"
  method(func_name).call *args
end

def square x
  puts x**2
end

def multiply a, b
  puts a*b
end

with_document :square, 5
with_document :multiply, 5, 3

это производит

about to call square(5)
25
about to call multiply(5, 3)
15

, который я Уверен, вы согласитесь, делает свою работу.

3
ответ дан 28 November 2019 в 03:24
поделиться

Python-подобные декораторы могут быть реализованы в Ruby. Я не буду пытаться объяснять и приводить примеры, потому что Иегуда Кац уже опубликовал в блоге хороший пост о декораторах DSL в Ruby, поэтому я настоятельно рекомендую прочитать его:

ОБНОВЛЕНИЕ: у меня есть пара голосов по этому поводу, поэтому позвольте мне объяснить подробнее.

alias_method (и alias_method_chain) - это НЕ то же самое, что и декоратор. Это просто способ переопределить реализацию метода без использования наследования (чтобы клиентский код не заметил разницы, по-прежнему используя тот же вызов метода). Это могло быть полезно. Но также это может быть подвержено ошибкам. Любой, кто использовал библиотеку Gettext для Ruby, вероятно, заметил, что ее интеграция с ActiveRecord нарушалась с каждым крупным обновлением Rails, потому что версия с псевдонимом следует семантике старого метода.

Назначение декоратора в целом НЕ состоит в том, чтобы изменять внутреннее устройство любого данного метода и при этом иметь возможность вызывать исходный метод из модифицированной версии, а в том, чтобы улучшить поведение функции. Вариант использования «вход / выход», который несколько близок к alias_method_chain , является лишь простой демонстрацией. Другой, более полезный вид декоратора может быть @login_required , который проверяет авторизацию и запускает функцию только в том случае, если авторизация прошла успешно, или @trace (arg1, arg2, arg3) , который может выполнять набор процедур трассировки (и вызываться с разными аргументами для оформления различных методов).

Назначение декоратора в целом НЕ состоит в том, чтобы изменять внутреннее устройство любого данного метода и при этом иметь возможность вызывать исходный метод из модифицированной версии, а в том, чтобы улучшить поведение функции. Вариант использования «вход / выход», который несколько близок к alias_method_chain , является лишь простой демонстрацией. Другой, более полезный вид декоратора может быть @login_required , который проверяет авторизацию и запускает функцию только в том случае, если авторизация прошла успешно, или @trace (arg1, arg2, arg3) , который может выполнять набор процедур трассировки (и вызываться с разными аргументами для оформления различных методов).

Назначение декоратора в целом НЕ состоит в том, чтобы изменять внутреннее устройство любого данного метода и при этом иметь возможность вызывать исходный метод из модифицированной версии, а в том, чтобы улучшить поведение функции. Вариант использования «вход / выход», который несколько близок к alias_method_chain , является лишь простой демонстрацией. Другой, более полезный вид декоратора может быть @login_required , который проверяет авторизацию и запускает функцию только в том случае, если авторизация прошла успешно, или @trace (arg1, arg2, arg3) , который может выполнять набор процедур трассировки (и вызываться с разными аргументами для оформления различных методов).

Вариант использования «вход / выход», который несколько близок к alias_method_chain , является лишь простой демонстрацией. Другой, более полезный вид декоратора может быть @login_required , который проверяет авторизацию и запускает функцию только в том случае, если авторизация прошла успешно, или @trace (arg1, arg2, arg3) , который может выполнять набор процедур трассировки (и вызываться с разными аргументами для оформления различных методов).

Вариант использования «вход / выход», который несколько близок к alias_method_chain , является лишь простой демонстрацией. Другой, более полезный вид декоратора может быть @login_required , который проверяет авторизацию и запускает функцию только в том случае, если авторизация прошла успешно, или @trace (arg1, arg2, arg3) , который может выполнять набор процедур трассировки (и вызываться с разными аргументами для оформления различных методов).

7
ответ дан 28 November 2019 в 03:24
поделиться

Ваше предположение верно.

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

PS: ваш код не определяет функцию внутри функция, но другая функция для того же объекта (да, это недокументированная функция Ruby)

class A
  def m
    def n
    end
  end
end

определяет как m , так и n на A.

NB: способ ссылки на функцию был бы

A.method(:m)
1
ответ дан 28 November 2019 в 03:24
поделиться

Хорошо, я снова нашел мой код, который выполняет декораторы в Ruby. Он использует псевдоним для привязки исходного метода к другому имени, а затем определяет новый, чтобы что-то напечатать и вызвать старый. Все это делается с помощью eval, так что его можно повторно использовать как декораторы в Python.

module Document
  def document(symbol)
    self.send :class_eval, """
      alias :#{symbol}_old :#{symbol}
      def #{symbol} *args
        puts 'going to #{symbol} '+args.join(', ')
        #{symbol}_old *args
      end"""  
  end
end

class A 
  extend Document
  def square(n)
    puts n * n
  end
  def multiply(a,b)
    puts a * b
  end
  document :square
  document :multiply
end

a = A.new
a.square 5
a.multiply 3,4

Edit: здесь то же самое с блоком (без проблем с манипуляциями со строками)

module Document
  def document(symbol)
    self.class_eval do
       symbol_old = "#{symbol}_old".to_sym
       alias_method symbol_old, symbol
       define_method symbol do |*args|
         puts "going to #{symbol} "+args.join(', ')
         self.send symbol_old, *args
       end
    end  
  end
end
1
ответ дан 28 November 2019 в 03:24
поделиться

Я полагаю, что соответствующей идиомой Ruby будет цепочка псевдонимов методов, которая активно используется Rails. В этой статье он также рассматривается как декоратор в стиле Ruby.

Для вашего примера это должно выглядеть так:

class Foo
  def square(x)
    puts x**2
  end

  def square_with_wrap(x)
    puts "I am going to square", x
    square_without_wrap(x)
  end

  alias_method_chain :square, :wrap
end

alias_method_chain вызывает переименовывает квадрат на square_without_wrap и делает square псевдонимом для square_with_wrap .

Я считаю, что Ruby 1.8 не имеет встроенного этого метода, поэтому вам придется скопируйте его из Rails, но 1.9 должен включать его.

Мои Ruby-Skills немного устарели, так что извините, если код на самом деле не работает, но я уверен, что он демонстрирует концепцию.

0
ответ дан 28 November 2019 в 03:24
поделиться

IMO mooware has the best answer so far and it is the cleanest, simplest and most idiomatic. However he is making use of 'alias_method_chain' which is part of Rails, and not pure Ruby. Here is a rewrite using pure Ruby:

class Foo         
    def square(x)
        puts x**2
    end

    alias_method :orig_square, :square

    def square(x)
        puts "I am going to square #{x}"
        orig_square(x)
    end         
end

You can also accomplish the same thing using modules instead:

module Decorator
    def square(x)
        puts "I am going to square #{x}"
        super
    end
end

class Foo
    def square(x)
        puts x**2
    end
end

# let's create an instance
foo = Foo.new

# let's decorate the 'square' method on the instance
foo.extend Decorator

# let's invoke the new decorated method
foo.square(5) #=> "I am going to square 5"
              #=> 25
3
ответ дан 28 November 2019 в 03:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: