티스토리 뷰

5.1 상속


상속을 사용하면 다른 클래스를 개선하거나 특수한 기능을 더한 클래스를 만들 수 있다.

자식 클래스를 생성하는 기본적인 원리는 간단하다. 먼저 자식 클래스는 부모 클래스의 모든 기능을 상속한다. 자식 클래스에서는 부모 클래스의 모든 인스턴스 메서드를 사용할 수 있다.


간단한 예제를 살펴보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Parent
  def say_hello
    puts "Hello from #{self}"
  end
end
 
= Parent.new
p.say_hello
 
# 자식 클래스 생성
class Child < Parent
end
 
= Child.new
c.say_hello
 
 
cs

결과값은 아래와 같다.




자식 클래스를 생성하고자 Child < Parent 문법을 사용했다. < 문법은 오른쪽에 있는 클래스를 부모로 하는 새로운 클래스를 정의한다는 의미이다. 더 작다는 의미를 가진 < 기호는 자식 클래스가 부모 클래스를 더 특수화했다는 것을 의미한다.



superclass 메서드는 수신자(특정 클래스) 의 부모 클래스를 반환한다.

1
2
3
4
5
6
7
8
9
class Parent 
end
 
class Child < Parent
end
 
Child.superclass # => Parent
 
 
cs



그렇다면 Parent 클래스에도 부모 클래스가 있을까? 바로 Object는 모든 클래스의 부모 클래스이다.

1
2
3
4
5
6
class Parent 
end
 
Parent.superclass # => Object
 
 
cs


상속을 사용하여 자식 클래스는 부모 클래스에게 처리를 위임할 수 있으며, 자식 클래스에서는 실질적인 훅 메서드만을 호출해 애플리케이션에 기능을 추가할 수 있다. 이것은 일반적인 디자인이지만 반드시 좋은 디자인은 아니다. 루비 코드의 기능을 공유하는 또 다른 방식에는 믹스인이 있다. 믹스인을 다루기에 앞서 루비의 모듈과 친해질 필요가 있다.



5.2 모듈

모듈은 메서드와 클래스, 상수를 함께 하나로 묶는 수단이다. 모듈은 다음과 같은 두 가지 장점이 있다.


1) 모듈은 이름 공간(namespace)을 제공해서 이름이 충돌하는 것을 막아준다.

2) 모듈은 믹스인(mixin) 기능을 구현하는 데 사용한다.



이름 공간(namespace)

루비로 점점 큰 프로그램들을 작성하기 시작하면, 자연스럽게 재사용 가능한 코드들의 묶음 즉, 범용적으로 사용할 수 있는 루틴들의 라이브러리를 만들게 된다. 그리고 이 코드들을 별도 파일로 분리하여, 다른 루비 프로그램에서도 함께 사용하게 된다.

이러한 코드들은 보통 클래스로 이루어지기 때문에, 각 클래스를 파일에 나눠 담을 것이다. 하지만 클래스 형태를 지니지 않는 코드들을 함께 묶어줘야 하는 경우도 있다.


만약에 trig.rb, moral.rb 라는 파일 모두에 sin 함수가 정의되어 있는데, 그 2개의 파일을 모두 포함해서 사용한다면 sin 함수가 두 곳에 정의가 되어 있게된다. 이에 대한 해답으로 모듈 구조를 사용한다.


모듈의 특징

1. 모듈 상수의 이름은 클래스 상수처럼 첫 문자를 대문자로 한다.

2. 모듈 메서드는 클래스 메서드처럼 정의한다.

3. 다른 프로그램에서 이 모듈을 사용하고자  한다면, 단순히 두 개의 파일을 불러와서(루비에서는 require) 참조.

4. 상수나 메서드의 이름을 모호하지 않게 하기 위해 메서드가 정의된 모듈의 이름을 먼저 적고 ::나 마침표 뒤에 메서드 이름을 적는다.

5. 클래스 메서드처럼 모듈의 이름과 점을 메서드 이름 앞에 붙여서 모듈 메서드를 호출한다.

6. 상수는 모듈의 이름과 두 개의 콜론을 이용하여 접근.

1
2
3
4
require_relative 'trig'
require_relative 'moral'
= Trig.sin(Trig::PI/4)
wrongdoing = Moral.sin(Moral::VERY_BAD)
cs



5.3 믹스인

모듈을 사용하는 또 다른 훌륭한 방법이다. 

"모듈 안에 인스턴스 메서드를 정의하면 어떻게 되는 것인가?"  

클래스 선언에 모듈을 포함할 수 있다. 모듈을 포함하면 이 모듈의 모든 인스턴스 메서드는 갑자기 클래스의 인스턴스 메서드처럼 동작하기 시작한다. 즉, 이 메서드가 클래스에 녹아서 섞여 버린(mixed in) 것이다. 믹스인 된 모듈은 실제로 일종의 부모 클래스처럼 동작한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module Debug
  def who_am_i?
   puts "#{self.class.name} (id :#{self.object_id}: #{self.name})"
  end
end
 
class Phonograph
  include Debug
  
  attr_reader :name
  def initialize(name)
    @name = name
  end
end
 
class EightTrack
  include Debug
 
  attr_reader :name
  def initialize(name)
    @name = name
  end
end
 
ph = Phonograph.new("jake").who_am_i?
et = EightTrack.new("jessica").who_am_i?
cs


이 코드에서 보면 Phonograph, EightTrack은 모두 Debug 모듈을 include 하였고 인스턴스 메서드처럼 사용한다. 


[include의 쓰임]

1. 루비의 include 구문은 단지 해당 모듈에 대한 참조를 만들 뿐이다.

2. 모듈이 분리된 파일에 있을 경우, 해당 파일을 require 해야한다.

3. include는 단순히 모듈의 인스턴스 메서드를 복사하는 것이 아니라, include 클래스에 포함될 모듈에 대한 참조를 만든다.



5.5 모듈 구성하기


믹스인의 인스턴스 변수

믹스인을 하게 되면 인스턴스 변수들은 어떻게 관리하는가??


믹스인에 적용해 보면, 클라이언트 클래스에 섞인 모듈이 클라이언트 객체에 인스턴스 변수를 만들고 attr_reader 등의 메서드를 이용해서 이 인스턴스 변수를 위한 접근자까지 만들어 줌.

아래의 예제에서 Observable 모듈은 이를 include한 모든 클래스에 @observer_list 인스턴스 변수를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
module Observable
  def observers
    @observer_list ||= []
  end
  def add_observer(obj)
    observers << obj
  end
  def notify_observers
    observers.each { |o| o.update }
  end
end
cs


하지만 위와 같은 인스턴스 변수 선언은 위험하다. 왜냐하면 믹스인의 인스턴스 변수가 호스트 클래스의 변수나 다른 믹스인의 변수와 충돌할 수 있다는 점이다. 

그래서 대부분의 경우 믹스인이 되는 모듈이 자신만의 인스턴스 데이터를 가지지 않는다. 접근자를 사용해서 클라이언트 객체로부터 데이터를 얻어오는 것이 일반적이다. 일반적으로 자신의 상태를 가지는 믹스인은 믹스인이라고 할 수 없다. 이는 클래스로 작성되어야 한다.


루비가 메서드를 찾는 방법

1. 객체의 클래스 그 자체에서 메서드를 찾는다

2. 클래스에 포함된 믹스인에서 메서드를 찾는다

3. 그 후 상위 클래스와 상위 클래스의 믹스인을 살핀다.

4. 클래스에 여러 개의 모듈이 믹스인 되어 있다면, 마지막에 포함된 것부터 찾는다.



댓글