인스턴스 메서드를 원숭이 패치할 때 새 구현에서 재정의된 메서드를 호출할 수 있습니까?
클래스에서 메서드를 원숭이 패치하고 있다고 가정하면 재정의된 메서드를 어떻게 호출할 수 있습니까? 를들면약간예 같은 거.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
먼저 개체로 이동합니다. 그러면 개체를 반환하고 호출할 수 있습니다.(UnboundMethod
s는 부를 수 없습니다. 왜냐하면 그들은 그들의 것을 알지 못하고 무엇을 해야 할지 모르기 때문입니다.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 |