모듈과 클래스(Modules and Classes): 네임스페이스와 상속
모듈과 클래스(Modules and Classes): 네임스페이스와 상속
Ruby의 모듈과 클래스는 비슷해 보이지만 역할이 달라요. 모듈은 코드를 묶어 주는 네임스페이스와 믹스인 기능을 제공하고, 클래스는 거기에 더해 상속까지 담당해요. 이번에는 모듈 정의부터 가시성, 상속, 싱글턴 클래스까지 차근차근 살펴볼게요.
본문
모듈(Modules)
모듈은 Ruby에서 두 가지 목적을 품어요. 네임스페이싱(namespacing) 과 믹스인(mix-in) 기능이죠. 네임스페이스는 코드를 패키지나 기능 단위로 조직해서, 일반적인 이름이 다른 패키지와 간섭받지 않게 막아줘요. 예를 들어 IRB 네임스페이스는 irb의 기능을 제공하면서 Context라는 흔한 이름이 충돌하지 않게 해줘요.
믹스인 기능은 여러 클래스나 모듈에 공통 메서드를 공유하게 해줘요. Ruby에 기본 포함된 Enumerable 믹스인 모듈은 each 메서드를 바탕으로 많은 열거 메서드를 제공하고, Comparable은 <=> 비교 메서드를 바탕으로 객체 비교를 가능하게 해줘요.
모듈 정의
모듈은 module 키워드로 만들어요.
module MyModule
# ...
end
모듈은 기능을 추가·변경·제거하려고 몇 번이든 다시 열 수 있어요.
module MyModule
def my_method
end
end
module MyModule
alias my_alias my_method
end
module MyModule
remove_method :my_method
end
중첩(Nesting)
모듈은 중첩될 수 있어요.
module Outer
module Inner
end
end
많은 패키지가 기능을 위한 네임스페이스로 최외곽 모듈(또는 클래스) 하나를 만드는 방식을 써요. 내부 모듈은 Outer(또는 클래스)가 이미 정의돼 있으면 ::로도 정의할 수 있어요.
module Outer::Inner::GrandChild
end
Outer와 Outer::Inner가 아직 정의돼 있지 않으면 이 코드는 NameError를 일으켜요.
스코프(Scope)
상수
접근 가능한 상수는 모듈 중첩(어떤 문법으로 정의했는지)에 따라 달라져요. 아래 예시에서 상수 A::Z는 B에서 접근 가능해요. A가 중첩의 일부이기 때문이죠.
module A
Z = 1
module B
p Module.nesting #=> [A::B, A]
p Z #=> 1
end
end
하지만 A 안에 중첩하지 않고 ::로 A::B를 정의하면, 중첩에 A가 포함되지 않아 NameError 예외가 나요.
module A
Z = 1
end
module A::B
p Module.nesting #=> [A::B]
p Z #=> raises NameError
end
최상위 레벨에 정의된 상수는 ::를 앞에 붙여서 참조할 수 있어요.
Z = 0
module A
Z = 1
module B
p ::Z #=> 0
end
end
메서드
클래스 메서드는 직접 호출할 수 있어요. 조금 헷갈릴 수 있는데, 모듈에 있는 메서드는 "모듈 메서드"가 아니라 종종 "클래스 메서드"라고 불러요. 클래스 메서드가 상수를 참조할 때는 스코프가 같으므로 메서드 밖에서 참조할 때와 같은 규칙을 따라요.
모듈에 정의된 인스턴스 메서드는 include 됐을 때만 호출할 수 있어요. 이런 메서드는 ancestors 목록을 통해 자신이 포함됐을 때 정의된 상수에 접근할 수 있어요.
module A
Z = 1
def z
Z
end
end
include A
p self.class.ancestors #=> [Object, A, Kernel, BasicObject]
p z #=> 1
가시성(Visibility)
Ruby에는 세 가지 가시성이 있어요. 기본값은 public으로, public 메서드는 어떤 객체에서든 호출할 수 있어요. 두 번째는 protected예요. protected 메서드를 호출할 때는 보내는 쪽(sender)이 그 메서드를 정의한 클래스나 모듈을 상속하거나 include 하고 있어야 해요. 그렇지 않으면 NoMethodError가 나요.
세 번째는 private예요. private 메서드는 리시버 없이, 또는 리시버가 리터럴 self일 때만 소유 클래스 안에서 호출할 수 있어요. 리터럴 self가 아닌 리시버로 private 메서드를 호출하면 NoMethodError가 나요.
class A
def without
m
end
def with_self
self.m
end
def with_other
A.new.m
end
def with_renamed
copy = self
copy.m
end
def m
1
end
private :m
end
a = A.new
a.without #=> 1
a.with_self #=> 1
a.with_other # NoMethodError (private method `m' called for #<A:0x000000...>)
a.with_renamed # NoMethodError (private method `m' called for #<A:0x000000...>)
alias와 undef
메서드를 앨리어싱하거나 정의 해제할 수도 있는데, 이런 동작은 모듈이나 클래스에만 국한되지 않아요. 자세한 내용은 miscellaneous 문서에서 다뤄요.
클래스(Classes)
모든 클래스는 모듈이기도 해요. 다만 클래스와 달리 모듈은 다른 모듈(또는 클래스)에 믹스인될 수 없어요. 모듈처럼 클래스도 네임스페이스로 쓸 수 있고, 클래스는 슈퍼클래스에서 메서드와 상수를 상속받아요.
클래스 정의
클래스를 만들려면 class 키워드를 써요.
class MyClass
# ...
end
슈퍼클래스를 지정하지 않으면 새 클래스는 Object를 상속받아요. 다른 클래스에서 상속받으려면 < 뒤에 클래스 이름을 써요.
class MySubclass < MyClass
# ...
end
빈 클래스처럼 설계되어 내장 메서드를 최소한만 담고 있는 특별한 클래스 BasicObject가 있어요. BasicObject를 쓰면 독립적인 상속 구조를 만들 수 있어요.
클래스를 다시 열 때는 슈퍼클래스가 일치해야 해요.
class C
end
class D < C
end
# OK
class D < C
end
# OK
class D
end
# TypeError: superclass mismatch for class D
class D < String
end
상속(Inheritance)
클래스에 정의된 메서드는 그 서브클래스에서도 호출할 수 있어요.
class A
Z = 1
def z
Z
end
end
class B < A
end
p B.new.z #=> 1
상수도 마찬가지예요.
class A
Z = 1
end
class B < A
def z
Z
end
end
p B.new.z #=> 1
메서드를 재정의하면 슈퍼클래스 메서드의 기능을 덮어쓸 수 있어요.
class A
def m
1
end
end
class B < A
def m
2
end
end
p B.new.m #=> 2
메서드 안에서 슈퍼클래스의 기능을 호출하려면 super를 써요.
class A
def m
1
end
end
class B < A
def m
2 + super
end
end
p B.new.m #=> 3
인자 없이 쓴 super는 서브클래스 메서드에 주어진 인자를 그대로 사용해요. 슈퍼클래스 메서드에 인자를 보내고 싶지 않으면 super()를, 특정 인자를 보내고 싶으면 super(2)처럼 직접 넘겨요. super는 서브클래스 메서드 안에서 몇 번이든 호출할 수 있어요.
싱글턴 클래스(Singleton Classes)
객체의 싱글턴 클래스(메타클래스 또는 eigenclass라고도 불러요)는 그 인스턴스 하나만을 위한 메서드를 담는 클래스예요. class << object로 접근할 수 있어요.
class C
end
class << C
# self is the singleton class here
end
가장 흔하게는 이런 모양으로 접근해요.
class C
class << self
# ...
end
end
이렇게 하면 def self.my_method라고 쓰지 않고도 클래스(또는 모듈)에 메서드와 속성을 정의할 수 있어요.