클래스에서 메소드를 원숭이가 패치한다고 가정하면 어떻게 재정의 메소드에서 재정의 메소드를 호출 할 수 있습니까? 즉 뭔가 조금super
예 :
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
클래스에서 메소드를 원숭이가 패치한다고 가정하면 어떻게 재정의 메소드에서 재정의 메소드를 호출 할 수 있습니까? 즉 뭔가 조금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
. Dependency Injection Design Pattern , Factory Method Design Pattern , Abstract Factory Design Pattern 또는 그 라인을 따라 무언가 를 사용하는 경우 훨씬 더 효과적 입니다.이 경우 변경해야 할 장소가 있기 때문입니다
예를 들어 객체의 생성을 제어 하지 않는 경우 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'
기본적으로 Foo
객체가 코드에 들어간 시스템 경계에서 다른 객체로 감싸고 코드의 다른 곳에서 원래 객체 대신 해당 객체 를 사용 합니다 .
stdlib Object#DelegateClass
의 delegate
라이브러리에서 헬퍼 메소드를 사용합니다 .
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
이 질문 에 대해 조금 썼다 : Ruby module prepend vs derivation
나는 어떤 사람들이 다음과 같은 것을 시도하고 (그리고 왜 이것이 OverOver에서 작동하지 않는지 묻습니다), 즉 include
믹스 인 대신 믹싱을하는 것을 보았습니다 prepend
.
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
불행히도, 그것은 작동하지 않습니다. 상속을 사용하므로 사용하는 것이 좋습니다 super
. 그러나, Module#include
믹스 인 삽입 위의 수단 상속 계층 구조의 클래스, 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'
이후이 매우 깨끗 old_bar
단지 지역 변수이며,이 클래스 본문의 끝에서 범위 밖으로 갈 것, 어디서나 액세스하는 것은 불가능 도 반사를 사용! 그리고 이후는 Module#define_method
블록을 취하고, 블록 (인 자신의 주변 어휘 환경을여 닫으 왜 우리가 사용하는 define_method
대신에 def
, 여기) 는 (그리고 단지 는) 아직에 액세스 할 수 있습니다 old_bar
그것은 범위 밖으로 사라 후에도.
간단한 설명 :
old_bar = instance_method(:bar)
여기서 우리는 bar
메소드를 UnboundMethod
메소드 객체 로 감싸서 로컬 변수에 할당합니다 old_bar
. 즉, 이제 bar
덮어 쓴 후에도 유지할 수있는 방법이 있습니다 .
old_bar.bind(self)
조금 까다 롭습니다. 기본적으로 Ruby (및 거의 모든 단일 디스패치 기반 OO 언어)에서 메소드는 self
Ruby 에서 호출되는 특정 수신자 오브젝트에 바인드됩니다 . 즉, 메소드는 항상 호출 된 오브젝트를 알고 있으며 그 오브젝트가 무엇인지 알고 self
있습니다. 그러나 우리는 클래스에서 직접 메소드를 가져 왔습니다. 그것이 무엇인지 어떻게 알 수 self
있습니까?
음, 우리가해야 할 이유입니다,하지 않는 bind
우리를 UnboundMethod
반환하며 먼저 객체에 Method
우리가 다음 호출 할 수있는 개체를. (을 UnboundMethod
모르면 무엇을해야할지 모르기 때문에 호출 할 수 없습니다 self
.)
그리고 우리는 무엇을해야 bind
합니까? 우리는 단순히 우리 bind
자신에게, 원래의 것과 똑같이 행동 bar
할 것입니다!
마지막으로에서 Method
반환 된 을 호출해야합니다 bind
. Ruby 1.9에는 ( .()
)에 대한 새로운 구문이 있지만 1.8 이상이라면 단순히 call
메소드 를 사용할 수 있습니다 . 그것이 .()
어쨌든 번역되는 것입니다.
다음은 이러한 개념 중 일부를 설명하는 몇 가지 다른 질문입니다.
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#alias_method_chain
.
전체 시스템이 아닌 일부 특정 장소에서만 다른 동작이 필요한 경우 구체화를 사용하여 원숭이 패치를 특정 범위로 제한 할 수 있습니다. 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!
이 질문에서 구체화를 사용하는보다 정교한 예를 볼 수 있습니다. 특정 방법에 원숭이 패치를 활성화하는 방법은 무엇입니까?
Ruby 커뮤니티가에 정착하기 전에 Module#prepend
이전 토론에서 종종 볼 수있는 여러 가지 아이디어가 떠있었습니다. 이 모두에 의해 가산됩니다 Module#prepend
.
한 가지 아이디어는 CLOS의 메소드 결합기 아이디어였습니다. 이것은 기본적으로 Aspect-Oriented Programming의 아주 작은 버전입니다.
같은 구문 사용
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
이전 콤비 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
사용하면 전화를 할 수 있습니다 오버라이드 (override) 방법 :
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
이것의 주요 문제는 이전 버전과 호환되지 않는다는 것입니다. 메소드가 호출 old
되면 더 이상 호출 할 수 없습니다!
super
prepend
ed mixin 의 재정의 방법 은 본질적 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
메소드를 입력하는 것은 prepend
ed mixin 에서 메소드를 대체하는 것과 같습니다 . super
같은 무시 방법 동작합니다에서 super
또는 old
이 제안한다.
bind
가 동일한 old_method
변수를 호출하면 어떻게됩니까 ?
UnboundMethod#bind
새롭고 다른을 반환 Method
하므로 다른 스레드에서 동시에 두 번 또는 두 번 호출하는지 여부에 관계없이 충돌이 발생하지 않습니다.
old
및 redef
? 내 2.0.0에는 그것들이 없습니다. 아, 루비로 만들지 않은 다른 경쟁 아이디어
앨리어싱 방법을 살펴보십시오.이 방법은 메서드 이름을 새 이름으로 바꿉니다.
자세한 내용과 시작점은이 대체 방법 기사 (특히 첫 번째 부분)를 참조하십시오. 루비 API 문서는 , 또한 (덜 정교한) 예를 제공합니다.
재정의 할 클래스는 원래 메서드가 포함 된 클래스 다음에 다시로드되어야하므로 재정의 require
할 파일에서 클래스를 다시로드해야합니다 .