Есть ли способ переопределить метод в блоке? - PullRequest
2 голосов
/ 30 апреля 2019

Допустим, я хотел puts большое количество строк в верхнем регистре , но для краткости я хотел писать puts(string) вместо puts(string.upcase) каждый раз.

Есть ли способ для меня переопределить puts только внутри блока , как это?

# This will puts as normal
puts "abc"
# => abc

# This will puts an upcased string, but it's too verbose
puts "abc".upcase
# => ABC

# I want to do something like this, which will override the puts method for code run within the block
def always_upcase_strings(&block)
  def puts(string)
    super(string.upcase)
  end
end

always_upcase_strings do 
  puts "abc"
  puts "def"
  puts "ghi"
end
puts "xyz"
# => ABC
# => DEF
# => GHI
# => xyz

Мой пример выше упрощен - я не использую puts в моемслучай, но метод, который я написал сам.

Ответы [ 2 ]

3 голосов
/ 01 мая 2019

На Вторые мысли , вот лучший ответ:

def always_upcase_strings(&block)
  anon_class = Class.new do
    def puts(str)
      super(str.upcase)
    end
  end
  anon_class.new.instance_eval(&block)
end

always_upcase_strings do
  puts "abc" #=> "ABC"
  puts "def" #=> "DEF"
  puts "ghi" #=> "GHI"
end

puts "xyz"   #=> "xyz"

Этот создает временный класс (анонимный, потому что ему не нужно имя)с желаемым переопределением метода.

Затем вызывается полученный блок в контексте этого экземпляра класса .

А также менее запутанный, чем мой "redefine-undefine-redefine метод "решение", этот подход имеет дополнительное преимущество в том, что он потоко-безопасный .Таким образом, вы не получите странного поведения, если, например, будете выполнять параллельные тесты при вызове метода.

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

1 голос
/ 01 мая 2019

Это немного сумасшедший; Я не рекомендую запускать что-то подобное в производстве ... Но мне было любопытно, и я нашел способ сделать это с помощью метапрограммирования ruby:

def always_upcase_strings(&block)
  original_puts = method(:puts)
  define_method(:puts) do |string|
    original_puts.call(string.upcase)
  end
  yield
  undef :puts
  define_method(:puts, original_puts.unbind)
end

always_upcase_strings do
  puts "abc" #=> "ABC"
  puts "def" #=> "DEF"
  puts "ghi" #=> "GHI"
end

puts "xyz"   #=> "xyz"

Что здесь происходит?

  • Сначала я беру объект puts Method и сохраняю его в переменной.
  • Затем заново определите puts до , вызовите его с желаемой модификацией. (Вы также можете сделать это с super(string.upcase), но вышеприведенное, я думаю, лучше иллюстрирует происходящее.)
  • Затем yield в блок, так что вы можете вызвать новую версию метода.
  • Далее undef метод (!!!), чтобы полностью избавиться от переопределения. (Но, к сожалению, это также удаляет оригинальный метод. Итак ...)
  • Наконец, переопределите метод puts путем перепривязки оригинала обратно к self.

Это абсолютная черная магия. Используйте на свой страх и риск.


Вместо этого мой совет в "реальном мире" будет:

def puts_upcase(str)
  puts str.upcase
end

puts_upcase "abc" #=> "ABC"
puts "xyz"        #=> "xyz"

Или, возможно, в вашем сценарии, если вы действительно хотите настроить поведение метода класса, возможно, Module#prepend - лучший инструмент для работы.

Чем проще решения, тем лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...