XSLT является трудным работать с, но как только Вы завоевываете его, у Вас будет очень полное понимание DOM и схемы. Если Вы также XPath, то Вы на Вашем пути к изучению функционального программирования и это подвергнет новым методам и путям о решении проблем. В некоторых случаях последовательное преобразование более мощно, чем процедурные решения.
Вот еще один подход, который устраняет проблему с конфликтами между именами методов с псевдонимами (ПРИМЕЧАНИЕ, мое другое решение, использующее модули для украшения, также является хорошей альтернативой, поскольку оно также позволяет избежать конфликтов):
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
является закрытием.
Хорошо, пришло время попытаться ответить. Я нацелен здесь именно на питонистов, пытающихся реорганизовать свой мозг. Вот некоторый хорошо документированный код, который (приблизительно) выполняет то, что я изначально пытался сделать:
#! /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.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)
Удачи!
Это немного необычный вопрос, но интересный. Сначала я настоятельно рекомендую вам не пытаться напрямую переносить свои знания 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
, который я Уверен, вы согласитесь, делает свою работу.
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)
, который может выполнять набор процедур трассировки (и вызываться с разными аргументами для оформления различных методов). Ваше предположение верно.
Лучше всего использовать псевдоним, чтобы связать исходный метод с другим именем, а затем определить новый, чтобы что-то напечатать и вызвать старый. Если вам нужно делать это неоднократно, вы можете создать метод, который сделает это для любого метода (однажды у меня был пример, но я не могу его найти сейчас).
PS: ваш код не определяет функцию внутри функция, но другая функция для того же объекта (да, это недокументированная функция Ruby)
class A
def m
def n
end
end
end
определяет как m
, так и n
на A.
NB: способ ссылки на функцию был бы
A.method(:m)
Хорошо, я снова нашел мой код, который выполняет декораторы в 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
Я полагаю, что соответствующей идиомой 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 немного устарели, так что извините, если код на самом деле не работает, но я уверен, что он демонстрирует концепцию.
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