루비

🧬 루비(Ruby) 객체 지향 레벨 업: 상속, 모듈, 믹스인 마스터! (Part 7)

프로그래밍 멘토 2025. 3. 30. 00:00

객체 지향의 기본기를 탄탄히 다지신 여러분, 환영합니다! 😄 지난 Part 6: 객체 지향 첫걸음에서는 클래스와 객체, 그리고 인스턴스 변수와 메소드, `initialize`까지 OOP의 핵심 개념을 배웠습니다. 이제 이 기본기를 바탕으로 코드를 더욱 유연하고 효율적으로 만드는 객체 지향의 고급 기술들을 탐험해 볼 차례입니다!

이번 파트에서는 객체 지향 프로그래밍의 강력한 무기들인 상속(Inheritance), 모듈(Module), 그리고 루비만의 특별한 매력 포인트인 믹스인(Mixin)에 대해 집중적으로 알아볼 거예요. 이 기능들을 활용하면 코드의 재사용성을 극대화하고, 프로그램을 더욱 체계적으로 구조화하며, 유연하게 기능을 확장할 수 있게 됩니다. 🧩🚀

조금 더 깊이 들어가는 내용이지만, 루비는 이마저도 우아하게 만들어 준답니다. 함께 객체 지향 레벨업을 시작해 볼까요?


👨‍👩‍👧‍👦 코드 재사용의 클래식: 상속 (Inheritance)

현실 세계에서 자식은 부모로부터 특징(유전자)을 물려받죠? 프로그래밍 세계의 상속도 이와 비슷합니다. 상속은 이미 만들어진 클래스(부모 클래스, Superclass, Base class)의 속성(인스턴스 변수)과 행동(메소드)을 그대로 물려받아 새로운 클래스(자식 클래스, Subclass, Derived class)를 만드는 기능입니다.

상속을 사용하면:

  • ✅ 기존 코드를 재사용하여 새로운 클래스를 쉽게 만들 수 있습니다.
  • ✅ 클래스 간의 관계를 명확하게 표현할 수 있습니다 (예: '고양이'는 '동물'이다).
  • ✅ 코드를 수정할 때 부모 클래스만 수정하면 자식 클래스에도 반영되어 유지보수가 편리해집니다.

루비에서 상속을 구현하는 방법은 클래스를 정의할 때 이름 뒤에 < 기호와 부모 클래스 이름을 적어주면 됩니다.


# 부모 클래스: Animal
class Animal
  def initialize(name)
    @name = name
  end

  def speak
    puts "동물이 소리를 냅니다."
  end

  def eat(food)
    puts "#{@name}이(가) #{food}을(를) 먹습니다. 냠냠!"
  end
end

# 자식 클래스: Dog (Animal 클래스를 상속받음)
class Dog < Animal # '< Animal' 이 부분이 상속!
  # Dog 클래스에는 아무것도 정의하지 않았지만,
  # 부모인 Animal의 initialize, speak, eat 메소드를 모두 물려받음!
end

# 자식 클래스: Cat (Animal 클래스를 상속받음)
class Cat < Animal
  # 부모의 메소드를 재정의(Override)할 수도 있어요!
  def speak
    puts "#{@name}이(가) 야옹~ 하고 웁니다. 🐈"
  end
end

# 객체 생성 및 테스트
my_dog = Dog.new("멍뭉이")
my_cat = Cat.new("야옹이")

my_dog.eat("사료") # 부모의 eat 메소드 사용 -> 멍뭉이이(가) 사료을(를) 먹습니다. 냠냠!
my_cat.eat("참치") # 부모의 eat 메소드 사용 -> 야옹이이(가) 참치을(를) 먹습니다. 냠냠!

my_dog.speak # 부모의 speak 메소드 사용 -> 동물이 소리를 냅니다.
my_cat.speak # 자식(Cat)에서 재정의한 speak 메소드 사용 -> 야옹이이(가) 야옹~ 하고 웁니다. 🐈
  

부모 기능 활용하기: `super`

자식 클래스에서 부모 클래스의 메소드를 완전히 덮어쓰는(재정의, Override) 대신, 부모의 기능을 먼저 호출하고 거기에 추가적인 기능을 덧붙이고 싶을 때가 있습니다. 이때 사용하는 것이 바로 super 키워드입니다.


class Bird < Animal
  def initialize(name, can_fly)
    super(name) # 부모 클래스(Animal)의 initialize 메소드를 먼저 호출하여 @name 설정!
    @can_fly = can_fly # Bird 클래스만의 인스턴스 변수 추가
  end

  def speak
    super # 부모의 speak 메소드를 먼저 호출
    puts "#{@name}이(가) 짹짹! 웁니다. 🐦" # 추가 행동
  end

  def fly
    if @can_fly
      puts "#{@name}이(가) 하늘을 납니다. 훨훨~"
    else
      puts "#{@name}은(는) 날 수 없는 새입니다..."
    end
  end
end

my_bird = Bird.new("짹짹이", true)
my_bird.eat("씨앗") # 부모의 eat 메소드
my_bird.speak     # 부모 speak + 자식 speak 모두 실행됨
my_bird.fly       # 자식(Bird) 고유의 메소드
  

super를 사용하면 부모 클래스의 코드를 재활용하면서 기능을 확장할 수 있어 매우 유용합니다!


🧩 관련 기능 묶어주기: 모듈 (Module) - 네임스페이스

모듈(Module)은 관련된 메소드나 상수(변하지 않는 값)들을 하나로 묶어주는 역할을 합니다. 클래스와 비슷해 보이지만, 모듈은 객체(인스턴스)를 직접 만들 수 없고 상속을 할 수도 없습니다.

모듈의 주요 용도 중 하나는 네임스페이스(Namespace)를 만드는 것입니다. 네임스페이스는 이름 충돌을 방지하기 위해 코드의 영역을 구분하는 방법입니다. 예를 들어, 여러 곳에서 'Helper'라는 이름의 기능을 사용하고 싶을 때, 각 기능들을 모듈로 감싸서 구분할 수 있습니다.

모듈은 `module` 키워드로 정의하고, 이름은 클래스처럼 대문자 CamelCase를 사용합니다.


module MathHelper
  PI = 3.14159 # 모듈 상수 정의

  def self.circle_area(radius) # 모듈 메소드 정의 (self. 사용)
    PI * radius * radius
  end
end

module StringHelper
  def self.reverse_and_upcase(text)
    text.reverse.upcase
  end
end

# 모듈 이름과 :: 연산자를 사용하여 접근
puts MathHelper::PI                     # 출력: 3.14159
puts MathHelper.circle_area(10)       # 출력: 314.159

puts StringHelper.reverse_and_upcase("hello") # 출력: OLLEH
  

모듈 메소드를 정의할 때는 메소드 이름 앞에 self. 을 붙여야 모듈 자체의 메소드로 인식됩니다. 모듈 이름과 :: (스코프 해결 연산자) 또는 . 을 사용하여 모듈 내부의 상수나 메소드에 접근할 수 있습니다.


💎 루비의 꽃! 능력 추가하기: 믹스인 (Mixin) - `include`

모듈의 또 다른 매우 중요하고 강력한 용도는 바로 믹스인(Mixin)입니다! 믹스인은 클래스에 모듈을 포함(include)시켜서, 해당 모듈에 정의된 메소드들을 마치 **자신의 인스턴스 메소드처럼** 사용할 수 있게 만드는 기능입니다.

상속은 'is-a' 관계(고양이는 동물이다)를 표현하는 데 적합하다면, 믹스인은 'has-a' 또는 'can-do' 관계(자동차는 '움직이는' 능력을 가졌다, 사람은 '수영하는' 능력을 가졌다)를 표현하는 데 아주 효과적입니다. 특히 루비는 다중 상속(여러 부모 클래스를 동시에 상속하는 것)을 지원하지 않기 때문에, 믹스인이 그 대안으로 널리 사용됩니다.


# '수영하는' 능력을 제공하는 모듈
module Swimmable
  def swim
    puts "#{self.class}이(가) 첨벙첨벙 수영합니다! 🏊" # self.class 로 현재 객체의 클래스 이름 확인
  end
end

# '날아다니는' 능력을 제공하는 모듈
module Flyable
  def fly
    puts "#{self.class}이(가) 하늘을 훨훨~ 납니다! 🕊️"
  end
end

# 클래스 정의
class Dog
  include Swimmable # Dog 클래스에 Swimmable 모듈을 믹스인!
  # 이제 Dog 객체는 swim 메소드를 사용할 수 있게 됩니다!
end

class Bird
  include Flyable # Bird 클래스에 Flyable 모듈을 믹스인!
end

class Duck
  include Swimmable
  include Flyable   # 오리는 수영도 하고 날 수도 있으니 둘 다 믹스인!
end

# 객체 생성 및 능력 테스트
my_dog = Dog.new
my_bird = Bird.new
my_duck = Duck.new

my_dog.swim   # 출력: Dog이(가) 첨벙첨벙 수영합니다! 🏊
# my_dog.fly  # 오류 발생! Dog는 Flyable을 믹스인하지 않았음

my_bird.fly   # 출력: Bird이(가) 하늘을 훨훨~ 납니다! 🕊️
# my_bird.swim # 오류 발생!

my_duck.swim  # 출력: Duck이(가) 첨벙첨벙 수영합니다! 🏊
my_duck.fly   # 출력: Duck이(가) 하늘을 훨훨~ 납니다! 🕊️
  

include 키워드를 사용하여 모듈을 클래스에 믹스인하면, 해당 모듈의 메소드들이 그 클래스의 인스턴스 메소드처럼 추가됩니다. 이를 통해 관련 없는 클래스들에도 공통된 '능력'이나 '행동'을 쉽게 부여할 수 있습니다. 정말 강력하죠? 💪


🔐 접근 권한 관리: 접근 제어자 (간단 소개)

클래스나 모듈 내부의 메소드들은 외부에서 접근할 수 있는 범위를 지정할 수 있습니다. 이를 **접근 제어자(Access Control)**라고 하며, public, protected, private 세 가지 종류가 있습니다.

  • public: 기본값이며, 어디서든 자유롭게 호출 가능합니다. (우리가 지금까지 만든 메소드들)
  • protected: 해당 클래스 및 그 자식 클래스 내부에서만 호출 가능합니다.
  • private: 해당 클래스 내부에서만 호출 가능하며, 객체를 통해 직접 호출할 수 없습니다. (예: my_object.private_method 불가)

class MyClass
  def public_method
    puts "저는 public 메소드예요."
    private_method # 클래스 내부에서는 private 메소드 호출 가능
  end

  private # 이 아래부터 정의되는 메소드는 private이 됩니다.

  def private_method
    puts "저는 내부에서만 사용되는 private 메소드예요."
  end
end

obj = MyClass.new
obj.public_method # 정상 작동

# obj.private_method # 이 코드는 오류 발생! (private 메소드는 외부에서 직접 호출 불가)
  

접근 제어자는 객체의 내부 구현을 숨기고(캡슐화) 안정성을 높이는 데 중요한 역할을 합니다. 지금은 이런 게 있다는 정도만 알아두고 넘어가셔도 괜찮습니다!


💡 Part 7 핵심 요약

  • 상속 (Inheritance): 부모 클래스의 속성과 행동을 물려받아 자식 클래스를 만드는 기능 (class Child < Parent). 코드 재사용성 UP! 'is-a' 관계 표현.
    • super: 자식 클래스에서 부모 클래스의 메소드를 호출할 때 사용.
  • 모듈 (Module): 관련된 메소드, 상수 등을 묶는 단위 (module MyModule ... end).
    • 네임스페이스: 이름 충돌 방지를 위해 코드를 그룹화 (MyModule::CONSTANT, MyModule.method).
  • 믹스인 (Mixin): 모듈을 클래스에 include하여 모듈의 메소드를 클래스의 인스턴스 메소드처럼 사용하게 하는 기능. 루비의 강력한 특징! 'has-a' 또는 'can-do' 관계 표현.
  • 접근 제어자: 메소드의 접근 범위를 지정 (public, protected, private). 캡슐화와 안정성 향상.

🚀 다음 단계로!

객체 지향 레벨업에 성공하신 것을 축하합니다! 🎉 상속, 모듈, 그리고 특히 루비의 강력한 믹스인 기능까지 배우셨어요. 이제 여러분은 단순히 코드를 작성하는 것을 넘어, 프로그램을 더욱 구조적이고 유연하게 설계할 수 있는 능력을 갖추게 되었습니다!

상속과 믹스인의 차이를 잘 이해하고, 언제 어떤 것을 사용해야 할지 고민해보는 연습을 하면 좋습니다. 다양한 예제를 통해 직접 코드를 작성해보세요!

다음 Part 8: 루비의 숨겨진 보석 (블록, Proc, 람다) 에서는 루비 문법의 유연성과 강력함을 제대로 보여주는 블록, Proc, 람다에 대해 알아볼 거예요. 메소드에 코드 덩어리를 전달하는 신기하고 재미있는 경험을 하게 될 테니 기대해주세요! 😉