Menu

문서정보

목차

class

클래스와 객체지향에 대한 내용은 링크를 참고한다. 언어를 막론하고 클래스의 개념은 크게 다르지 않으므로, 다른 객제 지향언어를 다룬 적이 있다면 쉽게 이해할 수 있을거다. java, python, c++ 경험이 있다면 금상첨화.

클래스 정의

모든 것은 객체다.

객체는 클래스로부터 만들어지는 구현물이다. 클래스는 사물의 "특성"과 "행동방식"을 정의해 놓은 일종의 명세서 같은걸로 보면 되겠다. 이 명세서대로 작동하는 구현물을 만들면 그게 객체가 된다.

루비 클래스

루비 클래스 블럭은 class와 end 키워드 사이에 위치한다. 특이한 점은 클래스 이름의 첫 글자는 반드시 대문자여야 한다는 것으로, 소문자로 시작하면 에러부터 발생한다.. 클래스는 다음의 구성요소를 가진다. 각각의 요소에 대해서는 따로 자세히 다루도록 한다.
  1. Class variable : 클래스가 가지는 변수. 클래스 영역의 변수이기 때문에, 클래스로 부터 만들어진 모든 인스턴스가 공유한다.
  2. Instance variable : 인스턴스 마다 가지는 변수. 인스턴스에 독립적인 값이다.
  3. Method : 외부로 부터 값을 입력받아서, 내부에 저장/연산 하고 연산한 결과를 출력하는 등의 "데이터를 조작"하는 일을 한다.
아래는 하나의 데이터와 하나의 메서드로 이루어진, 가장 단순한 형태의 클래스다.
class Myclass
    @name = nil
    def setname name
        @name = name
    end
    def output
        puts @name
    end
end

Object-specific classes

루비는 특정 객체로 부터 클래스를 만드는 것을 허용한다. 아래예제에는 두개의 String 객체들이 있다. 이 중 a 객체로부터 anonymous class를 만들었다.
#!/usr/bin/ruby
a="hello"
b=a.dup

class << a
    def to_s
        "The value is #{self}"
    end
    def twoTimes
        self+self
    end
end

puts a.to_s
puts a.twoTimes
puts b.to_s
"class << obj"는 객체 obj로 부터 새로운 클래스를 만들어라는 의미다.

아래와 같은 방법으로도 만들 수 있다.
a = "hello"
b = a.dup
def a.to_s
  "The value is '#{self}'"
end
def a.twoTimes
  self + self
end
a.to_s	        # "The value is 'hello'"
a.twoTimes	# "hellohello"
b.to_s          # "hello"

Mixin Modules

클래스는 모듈을 포함할 수 있다. 클래스는 모듈에 있는 메서드를 "인스턴스 메서드"처럼 사용할 수 있다. 슈퍼클래스의 메서드를 그대로 사용할 수 있는 것과 동일한 효과를 얻을 수 있다.
module SillyModule
  def hello
    "Hello."
  end
end
class SillyClass
  include SillyModule
end
s = SillyClass.new
s.hello       # "Hello."

다른 예
# cat support.rb
module Week
   FIRST_DAY = "Sunday"
   def Week.weeks_in_month
      puts "You have four weeks in a month"
   end
   def Week.weeks_in_year
      puts "You have 52 weeks in a year"
   end
end

#!/usr/bin/ruby
require_relative "support"
   
class Decade
include Week
   no_of_yrs=10
   def no_of_months
      puts Week::FIRST_DAY
      number=10*12
      puts number
   end
end
d1=Decade.new
puts Week::FIRST_DAY
Week.weeks_in_month
Week.weeks_in_year
d1.no_of_months

실행 결과
Sunday
You have four weeks in a month
You have 52 weeks in a year
Sunday
120

루비에서 module를 mixin 하는 아주 전형적인 예제
#!/usr/bin/ruby
module A
   def a1
        puts "a1"
   end
   def a2
        puts "a2"
   end
end
module B
   def b1
        puts "b1"
   end
   def b2
        puts "b2"
   end
end

class Sample
include A
include B
   def s1
        puts "s1"
   end
end

samp=Sample.new
samp.a1
samp.a2
samp.b1
samp.b2
samp.s1

Extending Objects

"Object#extend"를 이용해서 객체에 다른 모듈을 끼워넣는 방식으로 확장할 수 있다.
module Humor
  def tickle
    "hee, hee!"
  end
end
a = "Grouchy"    # String 객체 a를 만들었다.
a.extend Humor   # 객체 a에 Humor 모듈을 가져다 붙였다.
a.tickle         # Humor의 tickle를 인스턴스 메서드처럼 사용할 수 있다.

extend는 클래스안에서도 사용할 수 있다.

module Humor
  def tickle
    "hee, hee!"
  end
end
class Grouchy
  include Humor
  extend  Humor
end
Grouchy.tickle	 # "hee, hee!"
a = Grouchy.new
a.tickle	 # "hee, hee!"
클래스안에서 extend는 self.extend와 동일하며, 모듈의 메서드를 클래스 메서드처럼 사용할 수 있다.

객체지향적인 코드 ?

객체지향적인 코드란 어떤 코드를 의미하는가.

아래는 영문/숫자를 제외한 문자를 제거하는 함수다.
def to_alphanumerics(s)
	s.gsub /[^\w\s]/, ''
end
우리는 이 코드가 "객체지향 적이지 않다"는 것을 알고 있다. 데이터와 메서드가 서로 분리돼 있기 때문이다.

위의 코드를 "객체지향적"으로 변경했다.
class String
    def to_alphanumeric
        gsub /[^\w\s]/, ''
    end
end
이 코드를 이용하면, 문자열은 메서드를 이용해서 스스로를 조작할 수 있다. 데이터와 메서드의 융합. 예컨데, 대상이 아니라 스스로가 객체로서 목적이 되는 셈이다.
"abcd1234!@()".to_alphanumeric

코드안에서의 클래스 정의

루비는 클래스와 코드사이에 어떠한 차이도 없다. 따라서 코드 중간에 클래스를 정의할 수도 있다. 아래의 코드를 보자.
3.times to
	class C
		puts "Hello"
	end
end

코드어디에서나 정의할 수 있는 특성으로, 메서드가 서로 다른 "같은 이름의 클래스"를 선언해서 사용하는 것도 가능하다.
class D
    def x
        'x'
    end
end

class D
    def y
        'y'
    end
end

obj = D.new
puts obj.x
puts obj.y

이런류의 코드에 어디에 쓸모있느냐 하겠는데, 메타 프로그래밍에서 유용하게 응용할 수 있다.

Instance Variables

인스턴스 변수는 클래스의 인스턴스에서 사용하는 변수다. 객체 변수 혹은 멤버 변수라고 부릅니다. 다른 언어에서는 일반적으로 멤버 변수등의 이름으로 사용하는데, Ruby는 인스턴스 변수란 용어를 선호하는 것 같다. 여기에서는 인스턴스 변수라고 부른다.

인스턴스 변수는 변수 이름 앞에 @를 붙인다.
#!/usr/bin/ruby

class MyClass
  @one = 1
  def do_something
    @one = 2
  end
  def output
    puts @one
  end
end

instance = MyClass.new
instance.output
instance.do_something

실행결과는 다음과 같습니다.
nil
2
output 값이 nil이 나온다. 왜 1이 아니지라고 생각할 수 있겠다. 인스턴스 변수는 인스턴스에서만 사용할 수 있다. 루비에서 인스턴스 변수는 nil로 초기화 된다.

Accessor Methods

인스턴스 변수는 바깥에서 직접 접근할 수 없고, 메서드를 이용해서 접근해야 한다. 만약 바깥에서 직접 접근하고 싶다면 Accessor Methods를 만들어서 사용해야 합니다.
#!/usr/bin/ruby

class MyClass
  def initialize
    @foo=28
  end 
  def foo 
    return @foo
  end 
end

instance = MyClass.new
puts instance.foo
foo를 읽을 수 있다. 하지만 읽을 수만 있으며, 값을 쓰려고 하면 에러가 발생한다.
instance = MyClass.new
puts instance.foo
instance.foo = 496
foo에 496을 저장하려고 하면 다음과 같은 에러 메시지가 출력된다.
./class.rb:18: undefined method `foo=' for #<MyClass:0xb76f98b8 @foo=28> (NoMethodError)

이 문제를 해결하는 방법은 두 가지가 있다. 하나는 쓰기 접근이 가능한 Accessor Methods를 만드는 거다.
class MyClass
  def initialize
    @foo=28
  end
  def foo
    return @foo
  end

  def foo=(value)
    @foo = value
  end
end
간단한 방법이긴 하지만, 인스턴스 변수가 추가될 때마다 접근을 위한 메서드 코드를 만들어줘야 한다는 단점이 있다. 번거롭다. 이 경우 attr_accessor을 이용해서 접근자 메서드를 정의할 수 있다.
#!/usr/bin/ruby

class MyClass
  attr_accessor :foo
  def initialize
    @foo=28
  end
end

instance = MyClass.new
puts instance.foo
instance.foo = 496
puts instance.foo

하지만 Accessor 메서드를 사용할 경우 얻을 수 있는 장점이 있다. 메서드이기 때문에 연산을 위한 코드를 삽입할 수 있다. 입력 값을 반올림 해서 저장하는 코드다.
class MyClass
  def initialize
    @foo=28
  end

  def foo
    return @foo
  end

  def foo=(value)
    @foo = value.round
  end
end

instance = MyClass.new
puts instance.foo
instance.foo = 496.6
puts instance.foo

클래스 변수

클래스 변수는 이름앞에 @@연산자를 붙여서 만든다. 이 변수는 모든 클래스 인스턴스에서 함께 사용하는 변수다. C++이나 Java의 static 변수와 비슷하다고 볼 수 있겠다.
#!/usr/bin/ruby

class MyClass
  @@value =1
  def add_one
    @@value = @@value +1
  end

  def value
    @@value
  end
end

instanceOne = MyClass.new
instanceTwo = MyClass.new
puts instanceOne.value

instanceOne.add_one
puts instanceOne.value
puts instanceTwo.value
출력 값입니다.
1
2
2

Class instance Variables

클래스 메서드

클래스 메서드와 일반적인 메서드와 동일한 방법으로 선언합니다. self가 붙는 것으로 이 둘을 구분합니다. 클래스 메서드는 클래스 인스턴스를 만들지 않고도 사용할 수 있습니다. c++의 static 메서드와 비슷합니다. static 메서드와 마찬가지로 클래스 변수를 사용할 수 없습니다.
class MyClass
  def self.some_method
    puts 'something'
  end
end
MyClass.some_method

인스턴스

인스턴스는 클래스로 부터 만드는 객체로, 인스턴스가 만들어지는 과정을 instantiation이라고 합니다. 루비는 아래와 같이 인스턴스를 만듭니다.

anObject = MyClass.new(parameters)

클래스는 인스턴스를 생성할 때 자동으로 실행될 함수를 정의할 수 있는데요, 이 함수의 이름이 initialize입니다. C++의 생성자와 비슷하죠. 생성자와 마찬가지로 매개 변수를 가질 수 있습니다.
class MyClass
  def initialize(parameters)
  end
end

Declaring Visibility

루비 메서드들은 C++, Java와 같은 언어와 마찬가지로 public, private, protected 3개의 visibility 모드를 가지고 있습니다. hidden 방식이라고 보면 될 것 같은데요. hidden은 객체지향의 대표적인 특징이죠. hidden 속성을 조절함으로서, 객체는 속성과 메서드를 안전하게 운용할 수 있습니다.

Private

private는 메서드와 변수에 hidden 속성을 부여한다고 보시면 됩니다. 외부에서 접근할 수 없습니다. private로 선언된 변수는 메서드를 통해서만 간접적으로 제어가 가능합니다.

자동차라는 객체가 있다고 가정해 봅시다. 자동차는 속도라는 속성을 가지고 있을 건데요. 이걸 외부에서 직접 접근하도록 하면 안되겠죠. 논리적으로도 맞지 않습니다. 속도는 엑셀레이터와 브레이크를 통해서만 접근해야 겠죠. 클래스라면 엑셀 메서드와 브레이드 메서드를 이용해서 접근하도록 해야 하죠. 속도는 private로 숨겨야 하는 속성인 거죠.

루비에서의 private도 같은 목적으로 사용합니다. 메서드와 변수를 숨기기 위해서 사용합니다. private로 선언하는 방법은 다음과 같습니다.
  class Example
    def methodA
    end
 
    private # 아래의 모든 메서드들은 private 입니다. 
 
    def methodP
    end
  end
private 키워드를 단독으로 사용할 경우 아래의 모든 메서드들은 private가 됩니다. 선택적으로 적용하고 싶다면 아래와 같이 하면 됩니다.
  class Example
    def methodA
    end
 
    def methodP
    end
 
    private :methodP
  end
methodP를 private 속성으로 선언했습니다.

클래스 메서드의 경우에는 private_class_method를 사용할 수도 있습니다. 특이한 것은 new 메서드도 private로 선언할 수 있다는 건데요. 주로 singleton(:12) 클래스를 만들기 위해서 사용합니다. new를 사용하는 대신 사용자 정의 메서드를 사용하도록 강제하는 식이죠. new가 숨겨지므로 오류를 줄일 수 있습니다.

#!/usr/bin/ruby

class SingletonLike
  private_class_method :new
  @num 
  @@inst = nil 
  def initialize
    @num = 0 
  end 
  def SingletonLike.create
    @@inst = new unless @@inst
    return @@inst
  end 
  def add_one
    @num = @num+1
    return @num;
  end 
end

args = "1" 
block = nil 
mySingle1 = SingletonLike
mySingle2 = SingletonLike
puts mySingle1.create.add_one
puts mySingle2.create.add_one

public

public는 기본 hidden 속성입니다. 자유롭게 접근할 수 있습니다.

protected

C++이나 Java의 protected와 다르지 않습니다. 자신과 서브클래스들이 접근할 수 있으며, 외부에서는 접근할 수 없습니다. 이건 좀 자료를 더 찾아보고 정리해야 할듯

상속

클래스는 슈퍼 클래스 (부모 클래스 혹은 베이스 클래스라고도 한다.)로부터 메서드와 변수를 상속받을 수 있습니다. 루비는 다중 상속을 지원하지 않습니다. 단지 하나의 슈퍼 클래스만 가질 수 있습니다.
#!/usr/bin/ruby

class Car
  def transport_class
    puts 'Car'
  end
end

class Bus < Car
  def sub_class
    puts 'Bus'
  end
end

myCar = Bus.new
myCar.transport_class
myCar.sub_class
출력 :
Car
Bus

private 속성의 변수와 메서드가 아니라면 슈퍼 클래스로부터 자식 클래스로 상속 가능합니다.

부모 클래스의 메서드는 오버라이드(:12)할 수 있는데요. super 키워드를 이용해서 여전시 부모클 클래스의 메서드를 사용할 수 있습니다.
#!/usr/bin/ruby

class Car
  def iniitialize
  end
  def transport_class
    puts 'Car'
  end
end

class Bus < Car
  def sub_class
    puts 'Bus'
  end
  def transport_class
    super
    puts "This is My Car Class"
  end
end

Bus.new.transport_class
출력
Car
This is My Car Class

부모 클래스의 메서드에 대해서 alias를 줄 수 있는데, 이 기능을 이용하면 상속 메서드를 좀 더 자유롭게 사용할 수 있습니다.
#!/usr/bin/ruby

class Car
  def iniitialize
  end
  def transport_class
    "Car"
  end
end

class Bus < Car
  alias xCar transport_class
  def sub_class
    puts 'Bus'
  end
  def transport_class
    print xCar, " : This is My Bus\n"
  end
end

Bus.new.transport_class
출력
Car : This is My Bus

모듈 사용하기

모듈은 클래스와 마찬가지로 메서드, 다른 모듈, 클래스를 포함할 수 있습니다. 하지만 클래스는 아닙니다. 따라서 인스턴스를 만들거나 할 수는 없습니다. C++의 namespace와 비슷하다고 보면 될 것 같은데요.
  1. namespace : 새로 정의한 메서드 이름이 다른 곳에서 정의한 메서드와 충돌하지 않게 한다.
  2. 클래스 사이에 기능 공유 : 클래스와 모듈을 함께 (Mix in)사용하면, 마치 클래스에서 정의한 것 처럼 모듈의 메서드를 사용할 수 있습니다. 상속을 하지 않고도 모듈의 기능을 공유할 수 있습니다.
module A
  def a1
    puts 'a1 is called'
  end
end
 
module B
  def b1
    puts 'b1 is called'
  end
end
 
module C
  def c1
    puts 'c1 is called'
  end
end
 
class Test
  include A
  include B
  include C
 
  def display
    puts 'Modules are included'
  end
end
 
object=Test.new
object.display
object.a1
object.b1
object.c1
출력
Modules are include
a1 called
b1 called
c1 called