인스턴스 메서드를 원숭이 패치할 때 새 구현에서 재정의된 메서드를 호출할 수 있습니까?
클래스에서 메서드를 원숭이 패치하고 있다고 가정하면 재정의된 메서드를 어떻게 호출할 수 있습니까? 를들면약간예 같은 거.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_method에 def여기), 그것(그리고 그것만)은 여전히 접근할 수 있습니다.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
'programing' 카테고리의 다른 글
| URL/전화로 클릭할 수 있는 UI 레이블을 만드는 방법은 무엇입니까? (0) | 2023.06.01 |
|---|---|
| 스피너 텍스트 크기와 텍스트 색상을 변경하는 방법은 무엇입니까? (0) | 2023.06.01 |
| jQuery 연기 및 약속 - .then() vs.done() (0) | 2023.06.01 |
| 테이블 셀에서 CSS 텍스트 오버플로? (0) | 2023.06.01 |
| Windows 배치 파일에서 시스템 환경 변수를 설정하시겠습니까? (0) | 2023.06.01 |