[루비 사용자 가이드] 객체 초기화

루비/레일스 프로그래밍/루비 사용자 가이드 2009.02.06 10:45

예전에 살펴봤던 Fruit 클래스는 두개의 인스턴스 변수가 있습니다. 하나는 과일의 종류를 저장하며, 다른 하나는 과일의 상태를 저장합니다. 이 두개의 특성이 과일에서 빠져서는 안된다는 것(즉 둘 다 Fruit클래스의 인스턴스 객체에서 반드시 초기화 되어야만 한다는 것)을, 우리는 우리 자신만의 inspect 메소드를 작성해 보고 난 후에 알 수 있었습니다. 운좋게도, 루비는 인스턴스 변수가 항상 초기화될 수 있도록 할 수 있는 방법을 제공합니다.

initialize 메소드

루비가 새로운 객체를 생성할 때마다, initialize라는 이름의 메소드를 찾아서 실행하게 됩니다. 따라서, 우리는 간단히 initialize를 사용해 인스턴스 변수에 디폴트 값을 저장하도록 해서, inspect 메소드가 인스턴스 변수의 값을 제대로 볼 수 있도록 해 줄 수 있습니다.

ruby> class Fruit
    |   def initialize
    |     @kind = "apple"
    |     @condition = "ripe"
    |   end
    | end
   nil
ruby> f4 = Fruit.new
   "a ripe apple"

가정을 요구사항으로 바꾸기

디폴트 값이 그리 적절한 의미를 담지 못하는 경우가 있습니다. 예를 들어 과일의 디폴트 종류는 무엇일까요? 각각의 과일이 생성시 자신이 어떤 종류인지를 명시하도록 요구하는게 바람직할 것입니다. 이를 위해 우리는 initialize 메소드에 형식 인자(formal argument)를 추가할 수 있습니다. 여기서는 자세히 살펴보지 않겠지만, 어떤 이유로 인해서, 여러분이 new를 할 때 지정하는 인자들이 실제로는 initialize로 전달됩니다.

ruby> class Fruit
    |   def initialize( k )
    |     @kind = k
    |     @condition = "ripe"
    |   end
    | end
   nil
ruby> f5 = Fruit.new "mango"
   "a ripe mango"
ruby> f6 = Fruit.new
ERR: (eval):1:in `initialize': wrong # of arguments(0 for 1)

유연성있는 초기화

위에서 initialize 메소드에 한 번에 하나의 인자만 연관된 경우를 살펴보았습니다. 이 인자를 빼먹게 되면 에러가 발생합니다. 좀 더 사용자에게 사려깊게 코딩하려면, 인자가 주어진 경우에는 그 값을 사용하고, 그렇지 않은 경우에는 디폴트 값을 사용할 수 있습니다.

ruby> class Fruit
    |   def initialize( k="apple" )
    |     @kind = k
    |     @condition = "ripe"
    |   end
    | end
   nil
ruby> f5 = Fruit.new "mango"
   "a ripe mango"
ruby> f6 = Fruit.new
   "a ripe apple"

initialize 뿐 아니라, 아무 메소드에서나 디폴트 인자값을 지정할 수 있습니다. 이때 디폴트 값이 지정되는 인자가 인자 목록의 맨 끝 쪽으로 오도록 배치해야만 합니다.

때때로 객체를 초기화 하는 여러 방법을 제공하는 것이 유용할 때가 있습니다. 이 가이드에서 설명할 범위는 아니지만, 루비는 객체 리플렉션(reflection,역주:동적으로 객체 구조를 다루고, 객체/클래스를 생성하며, 메소드를 호출할 수 있도록 하는 기능)을 지원하고, 가변 길이 인자 목록을 지원합니다. 이 두 가지를 함께 활용하면 메소드 오버로딩의 효과를 낼 수 있습니다.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 접근자(Accessors)

루비/레일스 프로그래밍/루비 사용자 가이드 2009.02.06 10:44

접근자(Accessor)란 무엇인가?

예전에 인스턴스 변수에 대해 간략히 살펴보았지만, 자세히 다루지는 않았습니다. 객체의 인스턴스 변수는 객체의 어트리뷰트입니다. 즉, 인스턴스 변수는 어떤 객체를 동일한 클래스에 속하는 다른 객체와 구분하게 하는 것 중 하나입니다. 이러한 어트리뷰트를 읽고 쓸 수 있는 것은 매우 중요합니다. 그러한 작업(어트리뷰트 읽고 쓰기)에는
어트리뷰트 접근자(attribute accessors)라는 메소드가 필요합니다. 잠시 후에 반드시 접근자 메소드를 사용자가 작성해야 하는 것은 아님을 보게 되겠지만, 지금은 모든 작업을 직접 해 봅시다. 두 가지 종류의 접근자는 쓰기접근자(writers)읽기접근자(eaders)입니다.

ruby> class Fruit
    |   def set_kind(k)  # a writer
    |     @kind = k
    |   end
    |   def get_kind     # a reader
    |     @kind
    |   end
    | end
   nil
ruby> f1 = Fruit.new
   #<Fruit:0xfd7e7c8c>
ruby> f1.set_kind("peach")  # use the writer
   "peach"
ruby> f1.get_kind           # use the reader
   "peach"
ruby> f1                    # inspect the object
   #<Fruit:0xfd7e7c8c @kind="peach">

아주 단순하지요! 우리가 보고 있는 과일의 종류가 어떤 것인지 정보를 저장하고, 읽어올 수 있습니다. 하지만, 여기서 사용한 메소드 이름은 너무 복잡합니다. 아래와 같이 쓰는 것이 더 간단하면서 더 일반적일 겁니다.

ruby> class Fruit
    |   def kind=(k)
    |     @kind = k
    |   end
    |   def kind
    |     @kind
    |   end
    | end
   nil
ruby> f2 = Fruit.new
   #<Fruit:0xfd7e7c8c>
ruby> f2.kind = "banana"
   "banana"
ruby> f2.kind
   "banana"

inspect 메소드

잠깐 딴 이야기를 해 봅시다. 객체를 직접 들여다 볼 때, #<anObject:0x83678>처럼 암호같은 결과를 보게 됩니다. 이것은 단지 기본값으로 설정되어 있는 기능입니다. 여러분은 이 기능을 마음대로 바꿀 수 있습니다. 필요한 것은 inspect라 불리는 메소드를 정의하는 것 하나 뿐입니다. 이 메소드는 객체의 인스턴스 변수 전체(혹은 일부)의 상태를 포함하는 객체를 잘 설명할 수 있는 스트링을 반환해야 합니다.

ruby> class Fruit
    |   def inspect
    |     "a fruit of the #{@kind} variety"
    |   end
    | end
   nil
ruby> f2
   "a fruit of the banana variety"

앞의 메소드와 관련된 또 하나의 메소드는 to_s (스트링으로 변환하기)입니다. to_s는 객체를 출력할 때 쓰입니다. 일반적으로 inspect는 프로그램 작성과 디버깅을 위한 도구이고, to_s는 프로그램의 출력을 위한 방법입니다. eval.rbinspect를 결과 값을 표시하기 위해 사용합니다. p 메소드를 사용하면, 프로그램에서도 디버깅 출력을 얻을 수 있습니다.

# These two lines are equivalent:
p anObject
puts anObject.inspect

접근자를 만드는 쉬운 방법

많은 인스턴스 변수에 대해 접근자 메소드가 필요하기 때문에, 루비는 표준적인 형태로 접근자를 작성하는 쉬운 방법을 제공합니다.

Shortcut Effect
attr_reader :v def v; @v; end
attr_writer :v def v=(value); @v=value; end
attr_accessor :v attr_reader :v; attr_writer :v
attr_accessor :v, :w attr_accessor :v; attr_accessor :w

위 기능을 사용해 과일의 신선도를 추가해 봅시다. 우선, 자동으로 읽기접근자와 쓰기접근자를 작성하도록 할 수 있습니다. 이제 새로 추가된 정보를 inspect에도 넣어줍시다.

ruby> class Fruit
    |   attr_accessor :condition
    |   def inspect
    |     "a #{@condition} #{@kind}"
    |   end
    | end
   nil
ruby> f2.condition = "ripe"
   "ripe"
ruby> f2
   "a ripe banana"

과일로 장난하기

아무도 과일을 먹지 않으면, 우리가 만든 과일이 시간이 지남에 따른 변화를 가지도록 할 수 있습니다.

ruby> class Fruit
    |   def time_passes
    |     @condition = "rotting"
    |   end
    | end
   nil
ruby> f2
   "a ripe banana"
ruby> f2.time_passes
   "rotting"
ruby> f2
   "a rotting banana"

하지만, 이렇게 클래스를 변경하다 보니, 작은 문제가 발생하게 되었습니다. 만약 우리가 새로 과일을 만들게 되면 어떤 일이 벌어질까요? 인스턴스 변수는 값이 대입되기 전까지 존재하지 않는 다는 것을 기억에서 떠올려보십시오.

ruby> f3 = Fruit.new
ERR: failed to convert nil into String

여기서 오류가 발생한 부분은 inspect 메소드입니다. 이 오류는 정당한 것입니다. 우리는 어떤 과일의 종류와 상태를 알려달라고 요청을 했습니다. 하지만, 아직 f3 객체는 어떤 어트리뷰트에도 값을 대입하지 않은 상태입니다.
원한다면 inspect 메소드에서 defined? 메소드를 사용해서 인스턴스 변수가 정의되어 있는지를 확인한 후, 정의된 인스턴스 변수만 보고하도록 할 수도 있을 겁니다. 하지만 그런식으로 처리하는 것은 그리 유용하지 않을 겁니다. 모든 과일은 종류와 상태를 어트리뷰트로 가지고 있기 때문에, 우리는 항상 그 두 어트리뷰트가 정의되어 있다는 것을 확실히 할 수 있어야 할 것입니다. 그게 바로 다음에 다룰 내용입니다.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

티스토리 툴바