TL; DR - вы не можете вообще, но Base1.include Patch
может быть достаточно хорошим.
Для вашего примера кода ancestors
из Base1
и Base2
: (выровнены для ясности)
Base1.ancestors #=> [Base1, Feature, Object, Kernel, BasicObject]
Base2.ancestors #=> [Base2, Patch, Feature, Object, Kernel, BasicObject]
Base2
имеет дополнительный предок Patch
до Feature
- результат Feature.prepend Patch
.
Ruby не позволяет нам свободно изменять цепочку предков модуля, поэтому мы не можем просто задним числом Patch
- Feature
задним числом.
Но, к счастью, Patch
является первым модулем после класса Base
, поэтому мы можем прибегнуть к include
для добавления Patch
в Base1
вместо:
[111 ]
Очевидно, что это работает только для очень конкретных случаев, а не в целом.
Вот контрпример:
module Feature
def action ; 'Feature' ; end
end
module Foo
def action ; "#{super} overridden" ; end
end
module Patch
def action ; 'Patch' ; end
end
class Base1
include Feature
include Foo
end
Feature.prepend(Patch)
class Base2
include Feature
include Foo
end
Base1.new.action #=> "Feature overridden"
Base2.new.action #=> "Patch overridden"
Base1.include Patch
Base1.new.action #=> "Patch"
Глядя на предков, выявляется проблема:
Base1.ancestors #=> [Base1, Foo, Feature, Object, Kernel, BasicObject]
Base2.ancestors #=> [Base2, Foo, Patch, Feature, Object, Kernel, BasicObject]
Base1.include Patch
Base1.ancestors #=> [Base1, Patch, Foo, Feature, Object, Kernel, BasicObject]
Patch
и Foo
не в порядке.
Накладные расходы, добавленные с декоратор должен быть всего лишь одним дополнительным вызовом функции.
Работа, выполняемая декоратором, не является частью накладных расходов, поскольку ваша альтернатива - добавить эквивалентный код к декорированному объекту.
Таким образом, возможно, что выполнение функции decorate занимает в два раза больше времени, но это потому, что декоратор выполняет некоторую важную работу, на развлечение которой уходит примерно столько же времени, что и на функцию без декорирования.
Важно знать, что декоратор имеет простой эффект:
@decorator
def f():
…
- просто синтаксический сахар для
def f():
…
f = decorator(f)
Таким образом, если декоратор ничего не делает, вы не возникает никаких накладных расходов, когда вызывает декорированную функцию (хотя вызов decorator(f)
занимает немного времени), как в
decorator = lambda func: func
@decorator
def f():
…
. Если декоратор что-то делает, вы только получите все время, которое требует декоратор. Обычно это включает в себя дополнительный вызов функции (вызов декорированной функции), как в
def decorator(func):
def decorated_func():
print "Before calling function", func # Some overhead (but that's normal)
func() # This will be a second function call, after the call to decorated_func()
return decorated_func
. Таким образом, само по себе декорирование функции не добавляет много накладных расходов для того, что вы хотите сделать: единственные очевидные накладные расходы то, что вы могли бы в принципе удалить, означало бы не вызывать func()
в декорированной функции, а вместо этого копировать его полный код, но при этом пострадала бы читаемость кода (удобочитаемость и гибкость - вот некоторые причины, по которым декораторы существуют в первую очередь) .
Добавляют ли декораторы значительные накладные расходы в систему? Я не могу найти ничего, что могло бы подтвердить это.
Они почти не добавляют измеримых накладных расходов. Нуль.
Важно отметить, что декоратор запускается один раз для создания декорированной функции. Один раз.
Декорированная функция состоит из двух частей.
какое бы украшение ни было добавлено. Это не накладные расходы.
плюс исходная функция. Это не накладные расходы.
Нет никаких реальных накладных расходов. Вы могли бы - с некоторой осторожностью - измерить накладные расходы на один дополнительный вызов и возврат функции как часть декорированной функции, но это почти неизмеримо мало. И это, вероятно, гораздо меньше, чем альтернативный дизайн, в котором не используются украшения.