Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

method_missing을 사용하여 조상 체인 위로

오늘 우리는 조상 사슬을 따라 여행할 것이기 때문에 기내 수하물을 꼭 챙기십시오. 우리는 메소드 호출을 따라가서 그것이 어떻게 체인을 따라 올라가는지 볼 것이고 메소드가 없다면 어떤 일이 일어나는지 알아낼 것입니다. 그리고 우리는 불장난을 좋아하기 때문에 여기서 멈추지 않고 불장난을 계속할 것입니다. BasicObject#method_missing 재정의 . 주의를 기울이면 실제 예제에서도 사용할 수 있습니다. 보장은 없습니다. 가자!

선조 사슬

Ruby의 조상 체인의 기본 규칙부터 시작하겠습니다.

  • Ruby는 단일 상속만 지원합니다.
  • 또한 개체에 모듈 세트가 포함될 수 있습니다.

Ruby에서 조상 체인은 상속된 모든 클래스와 주어진 클래스에 대한 모듈의 순회로 구성됩니다.

Ruby에서 조상 체인이 어떻게 처리되는지 보여주는 예를 살펴보겠습니다.

module Auth end
 
module Session end
 
module Iterable end
 
class Collection
  prepend Iterable
end
 
class Users < Collection
  prepend Session
  include Auth
end
 
p Users.ancestors

생산:

[
  Session, Users, Auth,        # Users
  Iterable, Collection,        # Collection
  Object, Kernel, BasicObject  # Ruby Object Model
]

먼저 ancestors를 호출합니다. 주어진 클래스의 조상 체인에 접근하는 클래스 메소드.

Users.ancestors 다음을 포함하는 클래스 및 모듈의 배열을 순서대로 반환합니다.

  • Users의 추가 모듈
  • Users 수업
  • Users 클래스에 포함된 모듈
  • Collection 클래스의 추가 모듈 — Users의 직접 부모
  • Collection 수업
  • Collection 클래스에 포함된 모듈 — 없음
  • Object class — 모든 클래스의 기본 상속
  • Kernel module  -   Object에 포함됨 핵심 방법 보유
  • BasicObject class — Ruby의 루트 클래스

따라서 주어진 순회 클래스 또는 모듈의 표시 순서는 항상 다음과 같습니다.

  • 앞에 추가된 모듈
  • 클래스 또는 모듈
  • 포함된 모듈

객체나 클래스에서 메소드가 호출될 때 조상 체인은 주로 Ruby에 의해 순회됩니다.

메소드 조회 경로

메시지가 전송되면 Ruby는 메시지 수신자의 상위 체인을 탐색하고 주어진 메시지에 응답하는 것이 있는지 확인합니다.

조상 체인의 주어진 클래스나 모듈이 메시지에 응답하면 이 메시지와 관련된 메서드가 실행되고 조상 체인 탐색이 중지됩니다.

class Collection < Array
end
 
Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject]
 
collection = Collection.new([:a, :b, :c])
 
collection.each_with_index # => :a

여기에서 collection.each_with_index Enumerable 모듈에서 메시지를 수신합니다. 그런 다음 Enumerable#each_with_index 이 메시지에 대해 메서드가 호출됩니다.

여기에서 collection.each_with_index 가 호출되면 Ruby는 다음을 확인합니다.

  • 컬렉션은 each_with_index message에 응답합니다. => 아니요
  • 배열은 each_with_index message에 응답합니다. => 아니요
  • Enumerable은 each_with_index message에 응답합니다. =>

따라서 여기에서 Ruby는 상위 체인 탐색을 중지하고 이 메시지와 관련된 메서드를 호출합니다. 우리의 경우 Enumerable#each_with_index 방법.

Ruby에서는 이 메커니즘을 메서드 조회 경로라고 합니다. .

이제, 주어진 수신자의 조상 체인을 구성하는 클래스와 모듈이 메시지에 응답하지 않으면 어떻게 될까요?

BasicObject#method_missing

좋은 연주로 충분합니다! 개발자 스타일로 예외를 던지자. Collection을 구현하겠습니다. 클래스를 만들고 해당 인스턴스 중 하나에서 알 수 없는 메서드를 호출합니다.

class Collection
end
 
c = Collection.new
c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>

여기에서 Collection 클래스가 search를 구현하지 않습니다. 방법. 따라서 NoMethodError 제기된다. 그런데 이 오류가 발생하는 이유는 무엇입니까?

BasicObject#method_missing에서 오류가 발생합니다. 방법. 이 메서드는 메서드 조회 경로가 주어진 메시지에 해당하는 메소드를 찾지 못합니다.

좋아요... 하지만 이 메서드는 NoMethodError만 발생시킵니다. . 따라서 Collection 컨텍스트에서 메서드를 재정의할 수 있으면 좋을 것입니다. 수업.

BasicObject#method_missing 재정의 방법

뭔지 맞춰봐? method_missing을(를) 재정의해도 됩니다. 이 메서드는 메서드 조회 경로 메커니즘의 적용을 받기도 합니다. . 일반적인 방법과의 유일한 차이점은 이 방법이 메서드 조회 경로에 의해 적어도 한 번은 발견된다는 것입니다. .

실제로 BasicObject class — Ruby에 있는 모든 클래스의 루트 클래스 — 는 이 메서드의 최소 버전을 정의합니다. 클래식 루비 매직, n'est pa?

Collection class에서 이 메서드를 재정의해 보겠습니다. :

class Collection
  def initialize
    @collection = {}
  end
 
  def method_missing(method_id, *args)
    if method_id[-1] == '='
      key = method_id[0..-2]
      @collection[key.to_sym] = args.first
    else
      @collection[method_id]
    end
  end
end
 
collection = Collection.new
collection.obj1 = 'value1'
collection.obj2 = 'value2'
 
collection.obj1 # => 'value1'
collection.obj2 # => 'value2'

여기에서 Collection#method_missing @collection에 대한 위임자 역할 인스턴스 변수. 실제로 이것이 Ruby가 객체 위임 — c.f:delegate를 대략적으로 처리하는 방식입니다. 라이브러리.

누락된 메소드가 setter 메소드인 경우(collection.obj1 = 'value1' ), 메소드 이름(:obj1 )는 키로 사용되며 인수('value1' ) @collection 값으로 해시 항목(@collection[:obj1] = 'value1' ).

HTML 태그 생성기

이제 method_missing 이 메서드는 배후에서 작동하므로 재현 가능한 사용 사례를 구현해 보겠습니다.

여기에서 목표는 다음 DSL을 정의하는 것입니다.

HTML.p    'hello world'             # => <p>hello world</p>
HTML.div  'hello world'             # => <div>hello world</div>
HTML.h1   'hello world'             # => <h1>hello world</h1>
HTML.h2   'hello world'             # => <h2>hello world</h2>
HTML.span 'hello world'             # => <span>hello world</span>
HTML.p    "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>

이를 위해 HTML.method_missing 각 HTML 태그에 대한 메소드 정의를 피하기 위해 메소드.

먼저 HTML을 정의합니다. 기준 치수. 그런 다음 method_missing을 정의합니다. 이 모듈의 클래스 메서드:

module HTML
  def HTML.method_missing(method_id, *args, &block)
    "<#{method_id}>#{args.first}</#{method_id}>"
  end
end

우리의 메서드는 누락된 method_id를 사용하여 HTML 태그를 간단히 빌드합니다. — :div HTML.div 호출 , 예를 들어

클래스 메서드도 메서드 조회 경로의 적용을 받습니다.

다음과 같이 HTML 태그 생성기를 향상시킬 수 있습니다.

  • 중첩 태그를 처리하기 위해 블록 인수 사용
  • 단일 태그 처리 — <br/> 예를 들어

그러나 몇 줄의 코드로 엄청난 양의 HTML 태그를 생성할 수 있습니다.

요약하자면:

method_missing 대부분의 명령이 식별된 패턴 집합을 공유하는 DSL을 생성하기 위한 좋은 진입점입니다.

결론

우리는 Ruby의 조상 체인을 끝까지 진행하여 BasicObject#method_missing에 뛰어들었습니다. . BasicObject#method_missing Ruby Hook 메서드의 일부입니다. . 수명 주기의 정확한 순간에 개체와 상호 작용하는 데 사용됩니다. 다른 Ruby Hook 메서드와 마찬가지로 , 이 hook 방식은 주의해서 사용해야 합니다. 신중하게 말해서 Ruby Object Model의 동작을 수정해서는 안 됩니다. —놀거나 블로그 게시물을 작성하는 경우는 제외;-)

짜잔!