Ruby의 클래스 변수는 혼란스럽습니다. 전문 Ruby 사용자도 직관적으로 이해하기 어려울 수 있습니다. 가장 명백한 예는 무자비함과 관련이 있습니다.
class Fruit
@@kind = nil
def self.kind
@@kind
end
end
class Apple < Fruit
@@kind = "apple"
end
Apple.kind
# => "apple"
Fruit.kind
# => "apple"
kind
변경 자식 클래스의 변수는 부모의 변수도 변경합니다. 꽤 엉망입니다. 그러나 이것이 바로 언어가 작동하는 방식입니다. Smalltalk를 모방하기 위해 오래전에 내린 디자인 결정이었습니다.
심각해집니다
구현상의 단점만큼 아키텍처 선택이 아닌 것처럼 보이는 클래스 변수 이상함의 다른 예가 있습니다. 오늘은 그 중 제가 흥미롭게 생각하는 것에 대해 조금 이야기해 보겠습니다.
이제 두 개의 코드를 비교할 것입니다. 동일한 결과를 생성해야 하는 것처럼 보이지만 그렇지 않습니다.
첫 번째 예에서는 클래스 변수를 설정하고 이를 반환하는 메서드를 만듭니다. 여기에는 멋진 일이 전혀 없습니다. 모든 것이 예상대로 작동합니다.
class Foo
@@val = 1234
# This is shorthand for declaring a class method.
class << self
def val
@@val
end
end
end
Foo.val
# => 1234
아마도 당신은 이것을 몰랐지만 class << self
클래스 정의 안에 있을 필요는 없습니다. 아래 예에서 우리는 그것을 외부로 옮겼습니다. 클래스 메서드가 추가되었지만 클래스 변수에 액세스할 수 없습니다.
class Bar
@@val = 1234
end
class << Bar
def val
@@val
end
end
Bar.val
# warning: class variable access from toplevel
# NameError: uninitialized class variable @@val in Object
함수에서 클래스 변수에 액세스하려고 하면 경고와 예외가 발생합니다. 무슨 일이야?
어휘 범위 입력
나는 어휘 범위가 Ruby의 이상하고 혼란스러운 측면의 99%의 근원이라는 것을 점점 더 확신하게 되었습니다.
용어에 익숙하지 않은 경우를 대비하여 어휘 범위 지정은 추상 개체 모델에서 속해 있는 위치가 아니라 코드에서 나타나는 위치를 기준으로 항목을 그룹화하는 것을 의미합니다. 예를 보여주는 것이 훨씬 쉽습니다.
class B
# x and y share the same lexical scope
x = 1
y = 1
end
class B
# z has a different lexical scope from x and y, even though it's in the same class.
z = 3
end
클래스는 사전적으로 결정됨
그렇다면 클래스 변수 예제에서 어휘 범위는 어떻게 작동합니까?
글쎄요, 클래스 변수를 검색하기 위해 Ruby는 그것을 가져올 클래스를 알아야 합니다. 어휘 범위를 사용하여 클래스를 찾습니다.
작업 예제를 더 자세히 살펴보면 클래스 변수에 액세스하는 코드가 클래스 정의에 물리적으로 포함되어 있음을 알 수 있습니다.
class Foo
class << self
def val
# I'm lexically scoped to Foo!
@@val
end
end
end
작동하지 않는 예제에서 클래스 변수에 액세스하는 코드는 어휘 범위가 클래스로 지정되지 않습니다.
class << Bar
def val
# Foo is nowhere in sight.
@@val
end
end
그러나 클래스에 대해 어휘 범위가 지정되지 않은 경우 범위는 무엇입니까? Ruby가 출력하는 경고는 다음과 같은 단서를 제공합니다. warning: class variable access from toplevel
.
작동하지 않는 예제에서 클래스 변수는 어휘적으로 최상위 객체로 범위가 지정됩니다. 이로 인해 정말 이상한 동작이 발생할 수 있습니다.
예를 들어, 어휘 범위가 main으로 지정된 코드에서 클래스 변수를 설정하려고 하면 클래스 변수가 Object
에 설정됩니다. .
class Bar
end
class << Bar
def val=(n)
# This code is lexically scoped to the top level object.
# That means, that `@@val` will be set on `Object`, not `Bar`
@@val = i
end
end
Bar.val = 100
# Whaa?
Object.class_variables
# => [:@@foo]
더 많은 예
클래스의 어휘 범위 외부에서 클래스 변수를 참조하는 방법에는 여러 가지가 있습니다. 그들 모두는 당신에게 문제를 줄 것입니다.
다음은 즐거움을 위한 몇 가지 예입니다.
class Foo
@@foo = :foo
end
# This won't work
Foo.class_eval { puts @@foo }
# neither will this
Foo.send :define_method, :x do
puts @@foo
end
# ..and you weren't thinking about using modules, were you?
module Printable
def foo
puts @@foo
end
end
class Foo
@@foo = :foo
include Printable
end
Foo.new.foo
이제 왜 모든 사람들이 Ruby에서 클래스 변수를 사용하지 말라고 하는지 알 것입니다. :)