다음은 모듈 포함이 Ruby에서 작동하는 방식으로 작동하는 이유를 이해하는 데 필요한 메타 프로그래밍 개념을 설명하는 전체 스토리입니다.
모듈이 포함되면 어떻게됩니까?
모듈을 클래스에 포함 시키면 해당 모듈이 클래스의 조상 에 추가 됩니다. ancestors
메소드 를 호출하여 모든 클래스 또는 모듈의 조상을 볼 수 있습니다 .
module M
def foo; "foo"; end
end
class C
include M
def bar; "bar"; end
end
C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
# ^ look, it's right here!
의 인스턴스에서 메서드를 호출하면 C
Ruby는 제공된 이름 의 인스턴스 메서드 를 찾기 위해이 조상 목록의 모든 항목을 살펴 봅니다 . 우리가 포함 된 이후 M
로 C
, M
지금의 조상이며 C
우리가 호출 할 때 그래서, foo
인스턴스에 C
, 루비는 그 방법을 찾을 수 있습니다 M
:
C.new.foo
#=> "foo"
참고 것을 포함 클래스에 인스턴스 또는 클래스 메소드를 복사하지 않습니다 - 그것은 단지 그것은 또한 포함 된 모듈의 인스턴스 방법을 찾아야하는 클래스에 "주"를 추가합니다.
우리 모듈의 "클래스"메소드는 어떻습니까?
포함하면 인스턴스 메서드가 전달되는 방식 만 변경되기 때문에 모듈을 클래스에 포함 하면 해당 클래스 에서만 인스턴스 메서드를 사용할 수 있습니다 . 모듈의 "클래스"메서드 및 기타 선언은 클래스에 자동으로 복사되지 않습니다.
module M
def instance_method
"foo"
end
def self.class_method
"bar"
end
end
class C
include M
end
M.class_method
#=> "bar"
C.new.instance_method
#=> "foo"
C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class
Ruby는 클래스 메소드를 어떻게 구현합니까?
Ruby에서 클래스와 모듈은 일반 객체입니다. 클래스 Class
와 Module
. 즉, 동적으로 새 클래스를 만들고 변수에 할당 할 수 있습니다.
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
또한 Ruby에서는 객체에 대해 소위 싱글 톤 메소드 를 정의 할 수 있습니다. 이러한 메서드 는 개체의 숨겨진 특수한 싱글 톤 클래스 에 새 인스턴스 메서드로 추가됩니다 .
obj = Object.new
# define singleton method
def obj.foo
"foo"
end
# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]
그러나 클래스와 모듈도 단순한 객체가 아닌가? 사실 그들은 그렇습니다! 그것은 그들도 싱글 톤 방법을 가질 수 있다는 것을 의미합니까? 네, 그렇습니다! 그리고 이것이 클래스 메서드가 탄생하는 방법입니다.
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
또는 클래스 메서드를 정의하는보다 일반적인 방법은 self
생성되는 클래스 개체를 참조하는 클래스 정의 블록 내에서 사용 하는 것입니다.
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
모듈에 클래스 메소드를 어떻게 포함합니까?
방금 설정했듯이 클래스 메서드는 실제로 클래스 개체의 싱글 톤 클래스에 대한 인스턴스 메서드입니다. 이것은 우리 가 클래스 메소드를 추가하기 위해 싱글 톤 클래스 에 모듈을 포함 시킬 수 있다는 것을 의미합니까 ? 네, 그렇습니다!
module M
def new_instance_method; "hi"; end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
self.singleton_class.include M::ClassMethods
end
HostKlass.new_class_method
#=> "hello"
이 self.singleton_class.include M::ClassMethods
라인은별로 좋지 않아서 Ruby가 추가 Object#extend
했습니다. 동일한 기능을 수행합니다. 즉, 객체의 싱글 톤 클래스에 모듈을 포함합니다.
class HostKlass
include M
extend M::ClassMethods
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ there it is!
extend
호출을 모듈로 이동
이 이전 예제는 다음 두 가지 이유로 잘 구조화 된 코드가 아닙니다.
- 이제 모듈을 올바르게 포함하기 위해 정의 에서 와 둘 다를 호출해야합니다 . 유사한 모듈을 많이 포함해야하는 경우 매우 번거로울 수 있습니다.
include
extend
HostClass
HostClass
직접 참조 M::ClassMethods
입니다 구현 세부 모듈의 알거나 걱정 할 필요가 없습니다.M
HostClass
그래서 이것은 어떨까요 : 우리 include
가 첫 번째 줄에서 호출 할 때 , 우리는 모듈이 포함되었다는 것을 어떻게 든 알리고, 또한 extend
자신 을 호출 할 수 있도록 우리의 클래스 객체를 제공 합니다. 이렇게하면 원하는 경우 클래스 메서드를 추가하는 것이 모듈의 작업입니다.
이것이 바로 특별한 self.included
방법 입니다. Ruby는 모듈이 다른 클래스 (또는 모듈)에 포함될 때마다이 메서드를 자동으로 호출하고 호스트 클래스 객체를 첫 번째 인수로 전달합니다.
module M
def new_instance_method; "hi"; end
def self.included(base) # `base` is `HostClass` in our case
base.extend ClassMethods
end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
def self.existing_class_method; "cool"; end
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ still there!
물론 클래스 메서드를 추가하는 것이에서 할 수있는 유일한 작업은 아닙니다 self.included
. 클래스 객체가 있으므로 다른 (클래스) 메서드를 호출 할 수 있습니다.
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end