programing

인스턴스 메서드를 원숭이 패치할 때 새 구현에서 재정의된 메서드를 호출할 수 있습니까?

bestprogram 2023. 6. 1. 23:03

인스턴스 메서드를 원숭이 패치할 때 새 구현에서 재정의된 메서드를 호출할 수 있습니까?

클래스에서 메서드를 원숭이 패치하고 있다고 가정하면 재정의된 메서드를 어떻게 호출할 수 있습니까? 를들면약간예 같은 거.super

예.

class Foo
  def bar()
    "Hello"
  end
end 

class Foo
  def bar()
    super() + " World"
  end
end

>> Foo.new.bar == "Hello World"

편집: 제가 이 답을 쓴 지 9년이 지났고, 그것을 최신 상태로 유지하기 위해 성형수술을 받을 만합니다.

편집 전 마지막 버전은 여기에서 확인할 수 있습니다.


덮어쓴 메서드를 이름이나 키워드로 호출할 수 없습니다.그것이 원숭이 패치를 피해야 하고 대신 상속을 선호해야 하는 많은 이유 중 하나입니다. 분명히 재정의된 방법을 호출할 수 있기 때문입니다.

원숭이 패치 적용 방지

상속

따라서 가능하다면 다음과 같은 것을 선호해야 합니다.

class Foo
  def bar
    'Hello'
  end
end 

class ExtendedFoo < Foo
  def bar
    super + ' World'
  end
end

ExtendedFoo.new.bar # => 'Hello World'

은 이은만당생제작면다동니합어다한성을의 작성을 Foo물건들.그냥 모든 장소를 변경하면 다음을 만들 수 있습니다.Foo 대에다생니다합성을음신을 만듭니다.ExtendedFoo이것은 의존성 주입 설계 패턴, 공장 방법 설계 패턴, 추상 공장 설계 패턴 등을 사용하는 경우 훨씬 더 효과적입니다. 이 경우 변경해야 할 부분만 있기 때문입니다.

위임

생성을 제어하지 않는 경우Foo예를 들어, 객체는 사용자가 제어할 수 없는 프레임워크(: 루비 온 레일)에 의해 만들어지기 때문에 래퍼 설계 패턴을 사용할 수 있습니다.

require 'delegate'

class Foo
  def bar
    'Hello'
  end
end 

class WrappedFoo < DelegateClass(Foo)
  def initialize(wrapped_foo)
    super
  end

  def bar
    super + ' World'
  end
end

foo = Foo.new # this is not actually in your code, it comes from somewhere else

wrapped_foo = WrappedFoo.new(foo) # this is under your control

wrapped_foo.bar # => 'Hello World'

기적으로, 시의경서에, 기서계거템, 스본▁the▁basically.Foo객체가 코드로 들어와서 다른 객체로 포장한 다음 코드의 다른 모든 곳에서 원래 객체 대신 해당 객체를 사용합니다.

이것은 stdlib의 라이브러리에 있는 도우미 메서드를 사용합니다.

"깨끗한" 원숭이 패치

Module#prepend믹싱 프리펜딩

위의 두 가지 방법은 원숭이 패치를 방지하기 위해 시스템을 변경해야 합니다.이 섹션에서는 시스템을 변경할 수 없는 경우 선호되고 가장 덜 침입적인 원숭이 패치 방법을 보여줍니다.

Module#prepend 이 사용 사례를 거의 정확하게 지원하기 위해 추가되었습니다. Module#prepend와 같은 일을 합니다.Module#include단, 클래스 바로 아래의 혼합물에 혼합됩니다.

class Foo
  def bar
    'Hello'
  end
end 

module FooExtensions
  def bar
    super + ' World'
  end
end

class Foo
  prepend FooExtensions
end

Foo.new.bar # => 'Hello World'

에 대해서도 썼습니다: 또다대조해썼다금습니음에한저.Module#prepend다음 질문에서:루비 모듈 추가 대 파생

혼합 상속(중단)

는 몇몇 을 본 를 들어, "" (StackOverflow (StackOverflow않는 .include prepend ing it:

class Foo
  def bar
    'Hello'
  end
end 

module FooExtensions
  def bar
    super + ' World'
  end
end

class Foo
  include FooExtensions
end

유감스럽게도, 그것은 효과가 없을 것입니다. 즉, 할 수 . 왜냐하면 상속을 사용하기 때문입니다. 즉, 당신이 사용할 수 있다는 것을 의미합니다.super그러나 상속 계층 구조의 클래스 위에 혼합을 삽입합니다. 즉,FooExtensions#bar절대로 불리지 않을 것입니다 (그리고 만약 불렸다면,super실제로는 을 언급하지 않을 것입니다.Foo#bar 려히오에게.Object#bar존재하지 ), 왜냐하면 지않하음후이, 존재후.Foo#bar항상 먼저 발견됩니다.

메소드 래핑

큰 문제는 우리가 어떻게 그것을 고수할 수 있는가 하는 것입니다.bar실제 방법을 유지하지 않고 방법을 사용할 수 있습니까?답은 기능적 프로그래밍에 있습니다.우리는 메소드를 실제 객체로 파악하고 폐쇄(즉, 블록)를 사용하여 우리만 해당 객체를 유지하도록 합니다.

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  old_bar = instance_method(:bar)

  define_method(:bar) do
    old_bar.bind(self).() + ' World'
  end
end

Foo.new.bar # => 'Hello World'

은 매우: 이은매우깨다니합끗것다▁this▁since.old_bar로컬 변수일 뿐이며, 클래스 본문의 끝에서 범위를 벗어나며, 반사를 사용하더라도 어디에서도 액세스할 수 없습니다!블록을 사용하면 주변 어휘 환경에서 블록이 닫힙니다(이것이 바로 우리가 사용하는 이유입니다.define_methoddef여기), 그것(그리고 그것만)은 여전히 접근할 수 있습니다.old_bar범위를 벗어난 후에도.

간단한 설명:

old_bar = instance_method(:bar)

여기서 우리는 그것을 포장합니다.bar메서드를 메서드 개체로 지정하고 로컬 변수에 할당합니다.old_bar이것은, 우리가 이제 붙잡을 방법이 있다는 것을 의미합니다.bar덮어쓴 후에도.

old_bar.bind(self)

이거 좀 까다롭네요.기본적으로 Ruby(그리고 거의 모든 단일 발송 기반 OO 언어에서)에서 메서드는 다음과 같은 특정 수신기 객체에 바인딩됩니다.self다시 되었는지 알고, 메소드는 에 호출되었는지 알고, 메소드는 어떤 개체에 호출되었는지 있습니다.self 잡은 알 수요?self

글쎄요, 그렇지 않아요, 그래서 우리가 해야 할 일을 해야 합니다.UnboundMethod먼저 개체로 이동합니다. 그러면 개체를 반환하고 호출할 수 있습니다.(UnboundMethods는 부를 수 없습니다. 왜냐하면 그들은 그들의 것을 알지 못하고 무엇을 해야 할지 모르기 때문입니다.self.)

그리고 우리는 무엇을.bind래요그? 저희는 그냥.bind우리 자신에게, 그런 식으로 그것은 원래대로 행동할 것입니다.bar그랬을 거예요!

마지막으로, 우리는 전화할 필요가 있습니다.Method에서 된 것bind 1에는 그것을 몇 구문이 . (Ruby 1.9는 Ruby 1.9를 의미합니다.).()), 하지만 당신이 1.8이라면, 당신은 단순히 그 방법을 사용할 수 있습니다; 그것이 바로 그것입니다..()어쨌든 변환됩니다.

다음은 몇 가지 다른 질문으로, 이러한 개념 중 일부를 설명합니다.

"더러운" 원숭이 패치

alias_method 쇠사슬

원숭이 패치 적용의 문제는 메서드를 덮어쓰면 메서드가 사라져서 더 이상 호출할 수 없다는 것입니다.그러니, 그냥 백업 복사본을 만들자!

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  alias_method :old_bar, :bar

  def bar
    old_bar + ' World'
  end
end

Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'

문제는 우리가 이제 불필요한 이름 공간을 오염시켰다는 것입니다.old_bar 됩니다.이 방법은 문서에 표시되고 IDE의 코드 완료에 표시되며 반영 중에 표시됩니다.또한, 그것은 여전히 부를 수 있지만, 아마도 우리는 원숭이가 그것을 패치했을 것입니다. 왜냐하면 우리는 애초에 그것의 행동이 마음에 들지 않았기 때문입니다. 그래서 우리는 다른 사람들이 그것을 부르는 것을 원하지 않을 수도 있습니다.

이것은 몇 가지 바람직하지 않은 특성을 가지고 있음에도 불구하고 안타깝게도 AciveSupport를 통해 대중화되었습니다.

추가 사항:세련됨

전체 시스템이 아닌 몇 가지 특정 위치에서만 다른 동작이 필요한 경우 개선을 사용하여 원숭이 패치를 특정 범위로 제한할 수 있습니다.저는 여기서 그것을 사용하여 시연할 것입니다.Module#prepend위의 예:

class Foo
  def bar
    'Hello'
  end
end 

module ExtendedFoo
  module FooExtensions
    def bar
      super + ' World'
    end
  end

  refine Foo do
    prepend FooExtensions
  end
end

Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!

using ExtendedFoo
# Activate our Refinement

Foo.new.bar # => 'Hello World'
# There it is!

다음 질문에서 세분화를 사용하는 보다 정교한 예를 볼 수 있습니다.특정 방법에 대해 원숭이 패치를 활성화하는 방법은 무엇입니까?


버림받은 생각

Module#prepend이전 토론에서 가끔 언급되는 여러 가지 다른 아이디어가 떠돌고 있었습니다.이 모든 것은 다음과 같습니다.Module#prepend.

메서드 조합자

한 가지 아이디어는 CLOSE의 방법 조합자들의 아이디어였습니다.이것은 기본적으로 애스펙트 지향 프로그래밍의 하위 집합의 매우 가벼운 버전입니다.

다음과 같은 구문 사용

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end

  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar’s return value
  end
end

은 그걸실것의행에넣 "을어것다"의 을 "수 입니다.bar방법.

하지만 당신이 어떻게 접근할 수 있는지는 확실하지 않습니다.bar이 의수가 내에 있습니다bar:after아마도 우리는 그것을 사용할 수 있을 것입니다.super키워드?

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar:after
    super + ' World'
  end
end

교체

은 ▁toent와 같습니다.prepend호출하는 우선 방법과 혼합하기super방법의 맨 끝에마찬가지로, 애프터 콤비네이터는 다음과 같습니다.prepend호출하는 우선 방법과 혼합하기super방법의 맨 처음에

전화를 걸기 전과 후에 할 수도 있습니다.super전화하셔도 됩니다super번 및 을 모두 수행합니다.super 값,을 만드는 것, 의반기값, 들만환prepend메소드 결합기보다 더 강력합니다.

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end
end

# is the same as

module BarBefore
  def bar
    # will always run before bar, when bar is called
    super
  end
end

class Foo
  prepend BarBefore
end

그리고.

class Foo
  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar’s return value
  end
end

# is the same as

class BarAfter
  def bar
    original_return_value = super
    # will always run after bar, when bar is called
    # has access to and can change bar’s return value
  end
end

class Foo
  prepend BarAfter
end

old

이 아이디어는 다음과 유사한 새로운 키워드를 추가합니다.super덮어쓴 방법을 동일한 방식으로 호출할 수 있습니다.super재정의된 메서드를 호출할 수 있습니다.

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

이와 관련된 주요 문제는 하위 호환성이 없다는 것입니다: 만약 당신이 호출된 방법이 있다면.old당신은 더 이상 그것을 부를 수 없을 것입니다!

교체

super에 있어서 우선적인 방법으로.prepend에드 믹스인은 본질적으로 다음과 같습니다.old본 제안서에

redef

위와 유사하지만 덮어쓴 메소드를 호출하고 나가기 위한 새 키워드를 추가하는 대신def단독으로, 우리는 방법을 재정의하기 위한 새로운 키워드를 추가합니다.현재 구문이 잘못되었기 때문에 하위 호환성이 있습니다.

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  redef bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

두 개의 새로운 키워드를 추가하는 대신, 우리는 또한 의미를 재정의할 수 있습니다.super東京의 redef:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  redef bar
    super + ' World'
  end
end

Foo.new.bar # => 'Hello World'

교체

redef하는 것은 method를 하는 것과 .prepend에드 믹스 super에서는 우선방법다음같동이다작니합과은▁like다동▁in▁behaves니합처럼 동작합니다.super또는old본 제안서에

앨리어싱 방법을 살펴보십시오. 메소드의 이름을 새 이름으로 바꾸는 것과 같습니다.

자세한 내용과 시작점은 이 교체 방법 문서(특히 첫 번째 부분)를 참조하십시오.또한 Ruby API 문서는 (덜 정교한) 예를 제공합니다.

재정의할 클래스는 원래 메서드를 포함하는 클래스 이후에 다시 로드되어야 합니다.require파일에 있는 파일을 덮어씁니다.

언급URL : https://stackoverflow.com/questions/4470108/when-monkey-patching-an-instance-method-can-you-call-the-overridden-method-from