본문 바로가기
Programming/Ruby On Rails

[Ruby On Rails] Module Mixin (1) - include, prepend, extend

by 가론노미 2023. 4. 18.

Ruby는 다중 상속을 지원하지 않는다.

즉 Ruby 클래스는 하나의 부모 클래스(슈퍼 클래스)만 가질 수 있다는 뜻이다.

 

Ruby에서는 다중 상속을 모듈의 Mixin을 통해 구현이 가능하다.

Mixin에 대해 알아보기 전 Class와 Module의 차이점과 클래스 조상의 목록인 Ancestors에 대해 먼저 확인해보자.

Class vs Module

Class

객체 지향 프로그래밍에서 클래스는 클래스 인스턴스, 클래스 객체, 인스턴스 객체 또는 간단히 객체라고 하는 자체 인스턴스를 만드는 데 사용되는 구성이다.

클래스는 인스턴스가 상태와 동작을 가질 수 있도록 하는 구성 요소를 정의한다.

따라서 클래스는 객체 생성을 위해 만드는 것이다.

Module

루비에서 모듈은 “메서드와 상수의 모음”을 뜻한다.

따라서 모듈은 여러 클래스에서 사용할 수 있는 메서드를 제공하기 위해 만드는 것이다.

 

 


 

정리하자면, 클래스는 객체에 관한 것이며 모듈은 기능에 관한 것이다.

 

모듈은 클래스와 다르게 인스턴스를 생성할 수 없다.

모듈의 주 목적은 그 안에 정의한 메서드를 다양한 클래스에 include, prepend, extend를 통해 mixin 해서 재사용하는 것이다.

Ancestors

루비에서는 클래스가 생성될 때 ancestors 배열에 클래스 조상의 목록을 저장해둔다.

ancestors에는 이 클래스가 상속받는 모든 클래스, 자기 자신, 그리고 mixin된 모듈들이 포함된다.

String.ancestors 
# => [String, Comparable, Object, Kernel, BasicObject]

클래스의 인스턴스 메서드를 호출하면, ancestors 배열의 앞에서부터 메서드 정의를 찾는다.

 

Mixin

include

클래스의 인스턴스 메서드를 확장하여 모듈의 메서드를 사용할 수 있도록 한다.

include를 사용할 경우 해당 모듈은 원 클래스보다는 낮고 부모 클래스보다는 높은 우선순위를 가지게 된다.

module LogModule
  def log
    puts "log by LogModule"
  end
end

class BaseClass
  def log
    puts "log by BaseClass"
  end
end

class ServiceClass < BaseClass
  include LogModule
end

ancestors 배열을 확인해보면 MyModule이 원클래스인 MyClass의 뒤에 그리고 부모 클래스인 BaseClass보다는 앞에 위치해 있는 것을 볼 수 있다.

ServiceClass.ancestors 
# => [ServiceClass, LogModule, BaseClass, Object, Kernel, BasicObject]

prepend

클래스의 인스턴스 메서드를 확장하여 모듈의 메서드를 사용할 수 있도록 한다.

include와 다르게 prepend를 사용할 경우 해당 모듈은 원 클래스보다도 높은 우선순위를 가지게 된다.

 

즉, 클래스의 기존 메서드를 꾸며주는 역할을 한다.

아래와 같이 super 키워드를 조합하면 기존 메서드 앞이나 뒤에 원하는 동작을 추가할 수 있다.

module LogModule
  def log(agrs)
	puts "=== start loggging by LogModule ==="
	puts super # ServiceClass의 log 호출
    puts "=== finish loggging by LogModule ==="
  end
end

class ServiceClass
  prepend LogModule

  def log(args)
		args.inspect
  end
end

ancestors 배열을 확인해보면 MyModule이 원클래스인 MyClass보다도 앞에 위치해 있는 것을 볼 수 있다.

extend

위에서 본 include, prepend와 다르게 extend를 사용할 경우

클래스의 클래스 메서드를 확장하여 모듈의 메서드를 사용할 수 있도록 한다.

module LogModule
  def log
    "log by LogModule"
  end
end

class ServiceClass
  extend LogModule
end

ServiceClass.log
# => log by LogModule

extend는 클래스 메서드를 확장하기 때문에 클래스 메서드가 정의되는 싱글톤 클래스의 ancestors에서 MyModule을 확인할 수 있다.

ServiceClass.singleton_class 
# => #<Class:ServiceClass>
ServiceClass.singleton_class.ancestors 
# => [#<Class:ServiceClass>, LogModule, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

 

<참고>

https://matt.aimonetti.net/posts/2012-07-ruby-class-module-mixins/

https://spilist.github.io/2019/01/17/ruby-mixin-concern