'루비/레일스 프로그래밍/루비 사용자 가이드'에 해당되는 글 29건

  1. 2009.02.05 [루비 사용자 가이드] 변수
  2. 2009.02.05 [루비 사용자 가이드] 프로시저 객체
  3. 2007.05.24 [루비 사용자 가이드] 모듈
  4. 2007.05.13 [루비 사용자 가이드] 싱글턴 메소드
  5. 2007.05.07 [루비 사용자 가이드] 억세스 컨트롤
  6. 2007.05.06 [루비 사용자 가이드] 메소드 재정의
  7. 2007.05.06 [루비 사용자 가이드] 상속
  8. 2007.05.02 [루비 사용자 가이드] 클래스
  9. 2007.04.30 [루비 사용자 가이드] 메소드
  10. 2007.04.30 [루비 사용자 가이드] 객체 지향적 사고

[루비 사용자 가이드] 변수

루비/레일스 프로그래밍/루비 사용자 가이드 2009.02.05 20:31

루비에는 세 가지 종류의 변수와 한 가지 종류의 상수, 그리고 정확히 두 개의 유사변수(pseudo-variable)가 있습니다. 변수와 상수는 타입이 없습니다. 변수의 타입이 없는 것이 몇 가지
단점이 있긴 하지만, 장점이 더 많고 루비의 빠르고 쉬운(quick and easy) 철학에 어울리기 때문에
그렇게 정했습니다.

대부분의 언어에서는 변수의 타입과 변경 가능성(modifiability,상수인지 여부), 그리고 영역(scope)을 정하기 위해 미리 선언되어야 합니다. 루비에서는 변수의 선언이 필요하지 않습니다. 왜냐하면, 타입은 따로 신경써서 정의할 필요가 없고, 나머지 요소(변경 가능성과 영역)는 변수의 이름에서 명확히 드러나기 때문입니다.

식별자(identifier)의 첫번째 글자를 보면 변수가 어떤 종류의 것인지 한 눈에 알 수 있습니다.

$ 전역변수(global variable)
@ 인스턴스변수(instance variable)
[a-z] or _ 지역변수(local variable)
[A-Z] 상수(constant)

위의 이름 규칙에서 벗어나는 오직 두 가지 예외는 루비의 유사변수인 selfnil입니다. self는 항상 현재 실행중인  객체를 가리킵니다. nil은 초기화되지 않은 변수에 저장되는 의미가 없는 값입니다. 두 가지 모두 마치 지역변수인 것 처럼 이름이 붙어 있지만, self는 인터프리터에 의해 값이 관리되는 전역 변수이며 nil은 실제로는 상수입니다. 이름 규칙에서 이 둘만 예외이므로, 그리 혼란을 주는 일은 없을 겁니다.

selfnil에 값을 저장할 수 없습니다. mainself의 값으로 되어 있을 때는 탑레벨 객체를 가리킵니다.

ruby> self
   main
ruby> nil
   nil
Trackbacks 0 : Comments 0

[루비 사용자 가이드] 프로시저 객체

루비/레일스 프로그래밍/루비 사용자 가이드 2009.02.05 18:23

때로 예기치 않았던 이벤트에 대한 응답을 명시해야 할 필요성이 있습니다. 그런 목표는 다른 메소드에
코드의 묶음을 전달할 수 있을 때 쉽게 달성될 수 있습니다. 이를 위해서 코드를 마치 데이터처럼
다룰 수 있으면 좋을 것입니다.

새로운 프로시저 객체(rocedure object)proc을 사용해 만들 수 있습니다.

ruby> quux = proc {
    |   puts "QUUXQUUXQUUX!!!"
    | }
   #<Proc:0x4017357c>

이제 quux는 객체를 가리킵니다. 이 객체는 다른 모든 객체들과 마찬가지로 호출될 수 있는 여러 행동 방식을 가지게 됩니다. 특별히, 이 객체가 실행될 것을 call 메소드를 호출해 요청할 수 있습니다.

ruby> quux.call
QUUXQUUXQUUX!!!
   nil

그건 그렇고, quux가 메소드의 인자로 사용될 수 있을까요? 물론입니다.

ruby> def run( p )
    |   puts "About to call a procedure..."
    |   p.call
    |   puts "There: finished."
    | end
   nil
ruby> run quux
About to call a procedure...
QUUXQUUXQUUX!!!
There: finished.
   nil

trap 메소드는 우리가 어떤 시스템 시그널에 대한 응답을 정의할 수 있도록 합니다.

ruby> inthandler = proc{ puts "^C was pressed." }
   #<Proc:0x401730a4>
ruby> trap "SIGINT", inthandler
   #<Proc:0x401735e0>

일반적으로 ^C를 누르면 인터프리터가 중단되도록 합니다. 하지만 위 코드를 실행한 다음에는 메시지가 프린트되고 인터프리터는 계속 실행됩니다. 이에 따라 진행중이던 작업을 잃어버리지 않게 됩니다.(인터프리터 안에 영원히 갇혀 있을 걱정은 마십시오. exit를 입력하면 인터프리터를 종료시킬 수 있습니다.)

다른 주제로 가기 전에 마지막으로 한 가지 더 짚어두고 갈 것이 있습니다. 시그널에 프로시저 객체를 바인딩하기 위해, 그 프로시저 객체에 꼭 이름을 붙여야만 하는 것은 아닙니다. 다음과 같이 이름없는(nonymous) 프로시져 객체를 사용할 수 있습니다.

ruby> trap "SIGINT", proc{ puts "^C was pressed." }
   nil

위 문장은 다음과 같이 더 짧게 작성할 수도 있습니다.

ruby> trap "SIGINT", 'puts "^C was pressed."'
   nil

이런 형태는 간단한 이름 없는 프로시저 객체를 작성할 때 편하고 읽기 쉽게 해 줍니다.

Trackbacks 0 : Comments 0

[루비 사용자 가이드] 모듈

루비/레일스 프로그래밍/루비 사용자 가이드 2007.05.24 03:26

루비의 모듈은 클래스와 비슷합니다. 다른 부분은 아래와 같습니다:

  • 모듈의 인스턴스를 만들 수 없다.
  • 모듈의 하위 클래스를 만들 수 없다.
  • 모듈은 module ... end라는 키워드를 사용해 만들어진다.

실제로 모듈의 클래스인 Module은 클래스의 클래스인 Class의 상위 클래스입니다. 이해가 되시나요? 모르겠다고요? 계속 진행해 봅시다.

모듈의 전형적인 사용 방법은 두 가지입니다. 하나는 관련있는 상수와 메소드를 한 군데로 집중시켜 모아놓는 것입니다. 루비 표준 라이브러리의 Math 모듈이 그런 역할을 하고 있지요:

ruby> Math.sqrt(2)
   1.41421
ruby> Math::PI
   3.14159

:: 연산자는 루비 인터프리터에게 어떤 모듈에서 상수의 값을 참조할지를 말해줍니다(다른 모듈에서 PI라는 이름으로 다른 것을 가리키는 경우도 상상할 수 있습니다). 만약 우리가 어떤 모듈의 상수나 메소드를 :: 없이 직접 사용하기 바란다면, 그 모듈을 include 시키면 됩니다:

ruby> include Math
   Object
ruby> sqrt(2)
   1.41421
ruby> PI
   3.14159

모듈의 다른 사용 방법은 혼합(mixin)입니다. C++을 포함하는 몇몇 객체지향 언어에서는 다중 상속(multiple inheritance)을 지원합니다. 즉 상위 클래스가 둘 이상 있을 수 있다는 것이죠. 실제 세계에서 다중 상속의 예를 들어보자면 alarm clock(알람 시계)를 들 수 있습니다. 알람 시계는 시계(clocks) 클래스에 속하는 동시에 부저를 포함하는 물건(things with buzzers)에도 속합니다.

루비는 의도적으로 진정한 다중 상속을 구현하지 않았습니다. 다중상속에 대해서는 혼합(mixin) 기술이 훌륭한 대안입니다. 모듈이 인스턴스화 되거나 상속될 수 없다는 것을 기억하십시오. 그렇지만 우리가 클래스 정의에서 어떤 모듈을 include 하면, 그 모듈의 메소드가 실질적으로 그 클래스에 추가-혹은 "혼합"-됩니다.

혼합은 클래스에 부여하고 싶은 상세한 특징을 요청하는 방법이라고 생각할 수 있습니다. 예를 들어 만약 어떤 클래스가 잘 동작하는 each 메소드를 포함하고 있다면, 표준 라이브러리의 Enumerable 모듈을 혼합하면 자동으로 sortfind와 같은 메소드를 사용할 수 있게 됩니다.

이렇게 모듈을 사용함으로써, 다중 상속의 기본적인 기능을 제공하면서 클래스의 상속 관계는 단순한 트리 구조로 가져갈 수 있고, 그에 따라 언어의 구현을 굉장히 많이 단순하게 할 수 있습니다(자바의 설계자들도 비슷한 선택을 했습니다).

Trackbacks 0 : Comments 0

[루비 사용자 가이드] 싱글턴 메소드

루비/레일스 프로그래밍/루비 사용자 가이드 2007.05.13 07:22

한 인스턴스의 행동 방식은 그것의 클래스에 의해 결정됩니다. 하지만 때때로 특정한 인스턴스가 특별한 행동을 해야만 할 경우가 있을 수 있습니다. 대부분의 언어에서는 이를 위해 특별한 다른 클래스를 정의하고, 그 클래스를 단 한번만 인스턴스화해서 사용해야 합니다. 루비에서는 객체에게 그 객체 자신만의 메소드를 부여해 줄 수 있습니다.

ruby> class SingletonTest
    |   def size
    |     25
    |   end
    | end
   nil
ruby> test1 = SingletonTest.new
   #<SingletonTest:0xbc468>
ruby> test2 = SingletonTest.new
   #<SingletonTest:0xbae20>
ruby> def test2.size
    |    10
    | end
   nil
ruby> test1.size
   25
ruby> test2.size
   10

이 예에서 test1test2는 동일한 클래스에 속해 있습니다. 하지만 test2에는 size가 재정의되어 있기 때문에, test1test2의 동작이 다릅니다. 어떤 하나의 객체에만 주어진 메소드를 싱글턴(singleton) 메소드라고 합니다.

싱글턴 메소드는 서로 다른 버튼이 클릭되면 다른 행동이 발생해야 하는 그래픽 사용자 인터페이스(GUI)에서 자주 사용되곤 합니다.

싱글턴 메소드가 루비에만 있는 것은 아닙니다. CLOS, Dylan 등의 언어도 그런 능력을 제공합니다. 또한 Self나 NewtonScript 같은 몇몇 언어는 싱글턴 메소드만 제공하기도 합니다. 이런 언어들은 때때로 프로토타입 기반(prototype-based) 언어라 불립니다.

Trackbacks 0 : Comments 0

[루비 사용자 가이드] 억세스 컨트롤

루비/레일스 프로그래밍/루비 사용자 가이드 2007.05.07 02:12

앞에서 루비에는 함수가 없고 메소드뿐이라는 것을 이야기했습니다. 하지만 한 종류의 메소드만 있는 것은 아닙니다. 이번 장에서는 억세스 컨트롤(access control)에 대해 이야기하겠습니다.

클래스의 내부가 아닌 "최상위(top level)"에서 메소드를 정의하면 어떤 일이 벌어질지 생각해 봅시다. 그런 경우의 메소드를 C와 같은 전통적인 언어에서의 함수(function)와 유사한 것으로 생각할 수 있습니다.

ruby> def square(n)
    |   n * n
    | end
   nil
ruby> square(5)
   25

새로 정의한 메소드는 아무런 클래스에도 속하지 않는 것 같습니다. 하지만 실제로 루비는 그 메소드를 Object 클래스에 추가합니다. Object는 모든 다른 클래스의 상위 클래스입니다. 따라서 어떤 객체나 이 메소드를 사용할 수 있어야만 합니다. 이 명제는 참이긴 합니다만, 작은 함정이 숨어 있습니다. 즉 그 메소드가 모든 클래스의 사적(private) 메소드라는 것입니다. 나중에 이것이 어떤 의미인지 자세히 다룰것입니다만, 모든 클래스의 사적 메소드라는 점 때문에 이 메소드는 다음과 같이 항상 함수 스타일로 호출해야만 합니다:

ruby> class Foo
    |   def fourth_power_of(x)
    |     square(x) * square(x)
    |   end
    | end
  nil
ruby> Foo.new.fourth_power_of 10
  10000

다음과 같이 객체에 대해 이 메소드를 호출할 수 없습니다:

ruby> "fish".square(5)
ERR: (eval):1: private method `square' called for "fish":String

이 방식은 전통적인 언어와 동일한 방식으로 쓸 수 있는 함수를 제공하면서, 루비의 순수한 객체지향적인 성격(함수는 단지 객체의 메소드이며 수신 객체가 묵시적으로 self라는 것)을 교묘하게 유지해 줍니다.

객체지향 프로그래밍에서 일반적인 관습은, 앞에서도 힌트를 준 적이 있지만, 명세(specification)구현(implementation)의 분리, 혹은 객체가 어떤(what) 작업을 할 것인지와 어떻게(how) 그 작업을 완수할 수 있는가 사이의 분리입니다. 객체 내부에서 벌어지는 일은 사용자가 보지 못하게 숨겨져야만 합니다. 즉, 사용자는 어떤 입력이 들어가면 어떤 출력이 나오는지에만 신경을 써야 하며, 객체가 자기 내부에서 벌어지는 일에 대해서 잘 알고 있는 것으로 믿어야 합니다. 따라서 클래스가 외부에서는 볼 수 없는 메소드를 내부에서만 사용할 수 있는 것이(그리고 그 클래스에 속한 객체에 대한 사용자의 관점을 변화시키지 않으면서도, 그 메소드를 변경/개선 할 수 있는 것이) 유용할 때가 자주 있습니다. 다음에 있는 너무 뻔한 예제에서 engine이 눈에 보이지 않는 클래스의 내부 동작이라고 합시다.

ruby> class Test
    |   def times_two(a)
    |     puts "#{a} times two is #{engine(a)}"
    |   end
    |   def engine(b)
    |     b*2
    |   end
    |   private:engine  # 이렇게 해서 engine을 사용자로부터 숨긴다.
    | end
   Test
ruby> test = Test.new
   #<Test:0x4017181c>
ruby> test.engine(6)
ERR: (eval):1: private method `engine' called for #<Test:0x4017181c>
ruby> test.times_two(6)
6 times two is 12.
   nil

test.engine(6)이 12를 반환할 것을 기대했을 겁니다. 하지만 우리는 Test를 사용할 때, engine을 억세스 할 수 없다는 것을 배웠습니다. 오직 times_two과 같이 Test에 정의된 메소드 안에서만 engine을 사용할 수 있습니다. 우리는 외부에 알려진 인터페이스를 통해서 객체를 사용해야만 합니다. 이 경우 그 인터페이스에는 times_two 메소드만 들어 있습니다. 이 클래스를 작성한 프로그래머는 외부 사용자가 Test 객체와 상호작용 하는 방법을 변경하지 않으면서, engine을 마음대로 수정할 수 있습니다(아마도 여기서는 성능을 개선하기 위해 -곱셈이 덧셈보다 느리다면- b*2b+b로 변경할 수 있을겁니다). 이 예는 물론 유용성을 보여주기에는 너무 단순합니다. 억세스 컨트롤의 잇점은 더 복잡하고 재미있는 클래스를 만들 때 더 명확해 질 것입니다.

Trackbacks 0 : Comments 0

[루비 사용자 가이드] 메소드 재정의

루비/레일스 프로그래밍/루비 사용자 가이드 2007.05.06 20:17

하위 클래스에서 상위 클래스에 정의된 메소드를 재정의하여 하위 클래스 인스턴스의 행동 방식을 변경할 수 있습니다.

ruby> class Human
    |   def identify
    |     puts "I'm a person."
    |   end
    |   def train_toll(age)
    |     if age < 12
    |       puts "Reduced fare.";
    |     else
    |       puts "Normal fare.";
    |     end
    |   end
    | end
   nil
ruby> Human.new.identify
I'm a person.
   nil
ruby> class Student1<Human
    |   def identify
    |     puts "I'm a student."
    |   end
    | end
   nil
ruby> Student1.new.identify
I'm a student.
   nil

상위 클래스의 identify를 완전히 대치하지 않고 향상시키기 원하는 경우 super를 사용할 수 있습니다.

ruby> class Student2<Human
    |   def identify
    |     super
    |     puts "I'm a student too."
    |   end
    | end
   nil
ruby> Student2.new.identify
I'm a human.
I'm a student too.
   nil

super는 인자를 원래의 (상위 클래스에서 정의된) 메소드로 전달할 수 있는 기능을 제공합니다. 때때로 세상에는 두 종류의 인간이 있다고 합니다...

ruby> class Dishonest<Human
    |   def train_toll(age)
    |     super(11) # 요금을 조금만 냅니다.
    |   end
    | end
   nil
ruby> Dishonest.new.train_toll(25)
Reduced fare.
   nil

ruby> class Honest<Human
    |   def train_toll(age)
    |     super(age) # 전달받은 인자를 넘겨줍니다.
    |   end
    | end
   nil
ruby> Honest.new.train_toll(25)
Normal fare.
   nil

Trackbacks 0 : Comments 0

[루비 사용자 가이드] 상속

루비/레일스 프로그래밍/루비 사용자 가이드 2007.05.06 17:29

일상 생활에서 사물을 분류할 때는 자연스럽게 계층적으로 하게 됩니다. 우리는 모든 고양이가 포유류라는 것과 모든 포유류는 동물이라는 것을 압니다. 작은 클래스는 그들이 속한 더 큰 클래스루부터 특성을 상속(inherit)받습니다. 만약 모든 포유류가 숨을 쉰다면, 모든 고양이도 숨을 쉽니다.

이런 개념을 루비에서는 다음과 같이 씁니다:

ruby> class Mammal
    |   def breathe
    |     puts "inhale and exhale"
    |   end
    | end
   nil
ruby> class Cat<Mammal
    |   def speak
    |     puts "Meow"
    |   end
    | end
   nil

Cat이 어떻게 숨을 쉬어야 할지를 정확히 지정하지 않았지만, 모든 고양이는 그 행동을 Mammal로부터 상속받게 됩니다. 왜냐하면 CatMammal의 하위클래스로 지정되었기 때문입니다( 객체지향 용어로 더 작은 클래스는 하위클래스(subclass)이고 더 큰 클래스는 상위클래스(superclass)라고 합니다.) 프로그래머의 입장에서 보면 고양이는 숨을 쉴 수 있는 능력을 공짜로 얻는 셈입니다. speak 메소드를 추가하면 고양이는 숨도 쉬고 말도 할 수 있습니다.

ruby> tama = Cat.new
   #<Cat:0xbd80e8>
ruby> tama.breathe
inhale and exhale
   nil
ruby> tama.speak
Meow
   nil

상위 클래스의 몇몇 특성을 하위 클래스가 상속해지 말아야만 하는 경우도 있을 것입니다. 일반적으로 새는 날 수 있지만, 팽귄은 새면서도 날 수 없습니다.

ruby> class Bird
    |   def preen
    |     puts "I am cleaning my feathers."
    |   end
    |   def fly
    |     puts "I am flying."
    |   end
    | end
   nil
ruby> class Penguin<Bird
    |   def fly
    |     fail "Sorry. I'd rather swim."
    |   end
    | end
   nil

우리는 모든 새로운 클래스의 특성을 일일히 정의하지 않고, 상위 클래스와 하위 클래스간의 차이만을 다시 정의하거나 추가하면 됩니다. 이렇게 상속을 사용하는 것을 때때로 차이를 이용한 프로그래밍(differential programming)이라고 부르기도 합니다. 이런 식으로 객체의 클래스를 정의할 수 있는 것은 객체지향의 중요한 잇점 중 하나입니다.


Trackbacks 0 : Comments 0

[루비 사용자 가이드] 클래스

루비/레일스 프로그래밍/루비 사용자 가이드 2007.05.02 20:17

실제 세계는 물체(객체,Object)로 가득차 있습니다. 그리고 그 객체를 분류할 수 있습니다. 예를 들어 아기들은 개를 보면 그 품종과 상관 없이 "멍멍"하고 말합니다. 인간은 선천적으로 세상을 이런 구분을 지으면서 바라봅니다.

객체지향 프로그래밍 용어로 "개"와 같은 객체의 종류를 클래스(class)라 합니다. 그리고 어떤 클래스에 속한 특정 객체를 그 클래스의 인스턴스(instance)라고 합니다.

일반적으로 루비나 다른 객체지향 언어에서 객체를 만들 때는, 먼저 클래스의 특성을 정의하고, 그 다음에 그 클래스의 인스턴스를 만듭니다. 이 과정을 보여주기 위한 첫 예로 Dog 클래스를 정의해 봅시다.

ruby> class Dog
    |   def speak
    |     puts "Bow Wow"
    |   end
    | end
   nil

루비에서 클래스 정의는 키워드 classend 사이에 있는 코드 영역입니다. 그 영역 안의 def는 클래스의 메소드를 정의합니다. 앞의 글에서 살펴본 것 처럼, 메소드는 클래스에 속하는 객체의 구체적인 행동 방식에 해당합니다.

Dog 클래스를 정의했으니까, 개 객체를 하나 만들어 봅시다:

ruby> pochi = Dog.new
   #<Dog:0xbcb90>

우리는 Dog 클래스의 인스턴스를 하나 만들었습니다. 그리고, 그 객체에 pochi라는 이름을 붙였습니다. 어떤 클래스던 new 메소드는 그 클래스의 인스턴스인 객체를 새로 생성합니다. pochiDog의 객체이기 때문에, 앞에서 말한 클래스의 정의에 따라서, Dog가 가져야 한다고 정한 모든 특성을 부여받습니다. 앞에서 정의한 Dog의 특성은 매우 단순했습니다. 따라서 우리가 pochi에게 시킬 수 있는 동작은 다음 한 가지 뿐입니다.

ruby> pochi.speak
Bow Wow
   nil

어떤 클래스의 새 인스턴스를 만드는 것을 다른 말로는 클래스의 인스턴스화(instantiation)라고 합니다. 개와 이야기 하는 기쁨을 맛보려면 개가 한마리 있어야 합니다. 마찬가지로 Dog class에게 우리를 위해 짖으라는 요청을 할 수는 없습니다.

ruby> Dog.speak
ERR: (eval):1: undefined method `speak' for Dog:class

이것은 샌드위치라는 개념을 먹는 것이 아무 의미가 없는 것과 마찬가지입니다.

반면 특정 개에 이름을 붙이지 않고 개의 짖는 소리를 듣고 싶다면 순간적인 임시의 개를 생성할 수 있고, 그 개가 사라지기 전에 짓도록 명령할 수 있습니다.

ruby> (Dog.new).speak   # 보통 Dog.new.speak라고 더 많이 씀
Bow Wow
   nil

"잠깐": 의문이 들 수 있습니다. "나중에 사라지다니, 이 불쌍한 개에게 무슨 일이 벌어진거지?" 맞습니다. 만약 우리가 이름을 부여하지 않으면(pochi에게 했던 것 같이 말이죠), 루비의 자동화된 가비지 컬렉션이 그 개가 필요 없는 버려진 개라고 생각하고, 사정없이 그 객체를 없애버립니다. 여러분도 아시겠지만, 원하는 개를 항상 만들 수 있기 때문에 실제로는 아무런 문제가 없습니다.


Trackbacks 1 : Comments 0

[루비 사용자 가이드] 메소드

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.30 17:09

메소드란 무엇일까요? OO프로그래밍에서는 객체의 바깥에서 데이터를 직접 조작하는 것을 생각하지 않습니다. 대신 객체는 (그렇게 해 달라고 제대로 요청받았을 경우) 자신을 어떻게 조작할지에 대한 지식을 가지고 있습니다. 우리는 객체에 메시지를 전달하고, 그 메시지가 어떤 종류의 동작이나 의미있는 답을 일으킨다고 이야기하기도 합니다. 이런 일련의 일들은 객체 내부가 실제로 어떻게 동작하는지에 대해서 알거나, 주의를 기울일 필요 없이 이루어져야 합니다. 객체가 수행하도록 요청할 수 있는 동작(혹은 객체가 이해할 수 있는 메시지)을 객체의 메소드라 합니다.

루비에서는 도트 표기법을 사용해 메소드를 호출합니다(C++이나 자바와 동일합니다). 메시지를 전달받는 객체는 도트의 왼쪽에 표시됩니다

ruby> "abcdef".length
   6

직관적으로, 이 스트링 객체는 얼마나 긴지 질문을 받고 있습니다. 기술적으로, 우리는 "abcdef" 객체의 length 메소드를 호출하고 있습니다.

다른 객체는 length를 조금 다르게 해석하거나, 이해하지 못할 수도 있습니다. 어떤 메시지에 응답 하는 방법을 결정하는 것은 프로그램 실행 도중, 객체가 메시지를 받는 시점에 이루어지며, 어떤 동작이 일어나는지는 변수가 어떤 객체를 가리키고 있느냐에 따라 변할 수 있습니다.

ruby> foo = "abc"
   "abc"
ruby> foo.length
   3
ruby> foo = ["abcde", "fghij"]
   ["abcde", "fghij"]
ruby> foo.length
   2

length가 의미하는 바는 그 메시지를 받는 객체가 어떤 것이냐에 따라 달라 질 수 있습니다. 앞의 예에서 처음 foo 에 길이를 물어봤을 때, foo 가 단순한 스트링을 참조하고 있었고, 의미 있는 답은 한 가지 밖에 없었습니다. 두 번째 foo 는 배열을 가리키고 있고, 이때 논리적으로 가능한 답은 2 아니면 5나 10일 것입니다. 하지만 가장 일반적으로 수긍할만한 답은 물론 2 입니다(원한다면 다른 종류의 답을 낼 수도 있습니다).

ruby> foo[0].length
   5
ruby> foo[0].length + foo[1].length
   10

여기서 명심해야 할 것은 배열 객체가 배열이 되는 것이 어떤 의미인지를 알고 있다는 것입니다. 루비의 데이터는 그런 지식을 가지고 있기 때문에 외부의 요청을 적절한 방법으로 만족시켜줄 수 있습니다. 이런 구조는 프로그래머가 수 없이 많은 구체적인 함수 이름을 외우는 것을 막아줍니다. 왜냐하면 우리가 일상 언어로 표현할 줄 아는 개념에 대응하는, 상대적으로 적은 갯수의 메소드를 여러 종류의 데이터에 적용할 수 있고, 그 결과도 우리가 예상할 수 있는 것과 같을 것이기 때문입니다. 이런 OO 프로그래밍 언어의 특징을 (내 생각에 자바는 이런 부분을 별로 개발하지 못했습니다) 다형성(polymorphism)이라고 부릅니다.

객체가 이해할 수 없는 메시지를 받게 되면, 에러가 "발생(raise)"됩니다:

ruby> foo = 5
   5
ruby> foo.length
ERR: (eval):1: undefined method `length' for 5(Fixnum)

따라서 어떤 객체가 메소드를 처리하는 방법을 알 필요는 없지만, 그 객체가 어떤 메소드를 처리할 수 있는 지를 아는 것이 필요합니다.

메소드에 인자가 주어질 때는 보통 다음과 같이 괄호로 그 인자를 둘러쌉니다.

object.method(arg1, arg2)

하지만, 모호성이 없는 경우에는 괄호를 생략할 수도 있습니다.

object.method arg1, arg2

루비에는 self라는 특별한 변수가 있습니다. 그 변수는 메소드를 처리하는 객체를 참조합니다. 객체가 자기 자신의 메소드를 호출하는 일은 너무 자주 일어나기 때문에, 편의를 위해서 자기 자신의 메소드를 호출할 경우 "self."를 생략할 수 있습니다:

self.method_name(args...)

는 다음과 동일합니다.

method_name(args...)

전통적으로 함수 호출(function call)이라고 생각해 왔던 것들은 루비에서 단지 짧게 표기된 self에 대한 메소드 호출일 뿐입니다. 이렇게 해서 루비는 순수한 객체지향 언어로 남아있을 수 있습니다. 이런 함수같은 메소드는, 루비의 함수 호출이 어떻게 객체의 메소드 호출인지 알지 못하는 사람들이 편하게 사용할 수 있도록, 다른 프로그래밍 언어에서의 함수와 매우 유사하게 동작합니다. 원한다면 마치 객체의 메소드가 아닌 것 처럼 함수에 대해 말할 수 있습니다.


Trackbacks 0 : Comments 0

[루비 사용자 가이드] 객체 지향적 사고

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.30 14:46

객체지향(Object oriented)은 캐치프레이즈입니다. 무언가를 객체지향적이라고 말하는 것으로, 여러분이 아주 똑똑해 보이도록 할 수 있습니다. 루비는 객체지향적 스크립트 언어라고 주장합니다. 하지만, 도대체 "객체지향"이란 정확히 어떤 뜻일까요?

그 질문에 대해 다양한 답이 있어왔지만, 아마도 그 모두가 동일한 것으로 귀결될 것입니다. 결론을 성급히 말하기 보다, 전통적인 프로그래밍 패러다임에 대해서 잠깐 생각해 보도록 합시다.

전통적으로 프로그래밍 문제는 어떤 종류의 데이터 표현(data representation)과 그 데이터를 다루는 처리절차(procedure)를 가지고 공략되어 왔습니다. 이런 모델 아래서 데이터는 수동적이고, 스스로 동작하지 않으며, 무력한 존재입니다. 데이터는 단지 능동적이고, 논리적이며, 전능하고, 커다란 처리 절차가 명령하는 데로 바뀔 뿐입니다.

이런 접근 방법의 문제는 프로그램이 사람에 불과한, 머리속에 세세한 부분을 명확히 기억하는 것이 제한되어 있는, 프로그래머에 의해 작성된다는 것입니다. 프로젝트가 커져감에 따라, 절차적인 핵심 부분들은 어떻게 전체가 돌아가는지를 기억하는 것이 어려울 정도로 커져버립니다. 그에 따라 생각의 사소한 오류나 단순한 타이핑 실수가 찾기 어려운 버그의 근원이 될 가능성도 더 커져버립니다. 복잡하고 의도하지 않은 상호 작용이 절차 부분 내에서 생겨나기 시작하고, 그런 상호작용을 잘 처리하는 것은, 싱싱한 산낙지가 빨판을 입 천장에 붙이지 못하도록 막는 것 만큼이나 어려운 일입니다. 전통적인 프로그래밍 패러다임 하에서 버그를 최소화하고 국소화 하도록 도울 수 있는 여러 프로그래밍 가이드라인이 있습니다만, 작업하는 방법을 근본적으로 바꾸는 것을 포함하는 더 좋은 해결 방안이 있습니다.

객체지향 프로그래밍은 평범하고 반복적인 논리적 작업을 대부분 데이터 자체에 위임하는 것입니다. 따라서 데이터에 대한 개념이 수동적인 것에서 능동적인 것으로 바뀝니다. 다른 식으로 이야기하자면,

  • 각각의 데이터를 뚜껑이 열려 있어서, 그 안의 내용물을 꺼내거나 여러 물건을 넣을 수 있는 박스로 생각하는 것을 중단합니다.
  • 각각의 데이터를 뚜껑이 닫혀있고, 잘 표시된 몇 개의 스위치와 다이얼이 달려있는 잘 작동하는 기계로 다루는 것을 시작합니다.

앞에서 "기계"라고 설명한 존재의 내부는 단순할 수도 있고, 복잡할 수 있습니다. 하지만 내부가 복잡한지 단순한지를 외부에서 알 수는 없으며, 기계를 열어보는 것도 (그 기계의 설계가 무언가 잘못되었다는 것이 절대적으로 확실하지 않은 한) 허용되지 않습니다. 따라서, 데이터를 처리하기 위해 스위치를 켜고 끄거나, 다이얼을 읽는 것과 같은 일만 하도록 허용됩니다. 일단 그런 기계가 만들어지면, 기계 내부가 어떻게 돌아가는지에 대해서 관심을 가질 이유가 없습니다.

이 이야기를 듣고, 아마도 쓸데 없는 일을 일부러 더 만들어내고 있다고 생각할수도 있습니다. 하지만, 이런 접근 방법은 모든 일이 한꺼번에 잘못되는 것을 멋지게 막을 수 있습니다.

실제적 가치는 없지만, 앞에서 설명한 개념의 적어도 일부분라도 보여주는 단순한 예를 들겠습니다. 자동차에는 주행 거리계가 있습니다. 그 목적은 가장 마지막으로 리셋 버튼이 눌린 시점부터 차가 얼마나 많은 거리를 주행했는지 저장하는 것입니다. 이것을 프로그래밍 언어로 어떻게 모델링할까요? C언어에서 주행 거리계는 단순한 수를 저장하는 변수이고, 아마도 float 타입으로 할 수 있을겁니다. 프로그램은 그 변수를 조금씩 증가시키고, 때때로 필요할 때 0으로 초기화하면서 사용할 것입니다. 이 구조에서 잘못된 것이 무엇일까요? 수많은 이유로 인해 버그가 잘못된 값을 변수에 대입할 수 있습니다. C로 프로그램을 짜 본 사람이라면 며칠이나 버그를 잡았는데, 결국에는 어이없게도 너무나 단순한 이유로 인해서 그 버그가 발생했음을 알게된 허탈한 경우를 겪어보았을 것입니다(그런 버그를 찾는 그 순간은 보통 이마를 때리는 찰싹 소리로 알 수 있습니다).

똑같은 문제가 객체지향적인 환경에서는 확연히 다른 방식으로 처리될 수 있을겁니다. 프로그래머가 주행 거리계를 디자인할 때, 첫번째 질문은 "이것과 가장 잘 들어맞는 나에게 익숙한 데이터타입이 뭐가 있을까?"가 아니고, "이것이 정확히 어떻게 동작해야 할까?"입니다. 그 둘은 심오한 차이가 있습니다. 정확히 주행 거리계가 어떤 것이고, 외부 세계가 그 주행거리계와 어떻게 상호작용 하는지를 결정하는 것은 약간의 시간이 필요합니다. 우리는 증가시키고, 리셋할 수 있고, 값을 읽을 수 있지만 다른 기능은 제공하지 않는 작은 기계를 만들기로 결정합니다.

우리는 주행 거리계에 특정 값을 지정하는 방법을 제공하지 않습니다. 왜일까요? 왜냐하면 주행 거리계는 그렇게 동작하지 않기 때문입니다. 주행거리계를 가지고 할 수 있는 일은 몇 가지 되지 않고, 우리가 허용하는 기능은 그게 전부입니다. 따라서 만약 프로그램에서 무언가가 주행 거리계의 값을 실수로 다른 어떤 값으로 변경하려고 한다면(예를 들어 자동차의 온도 조절 시스템의 목표 온도), 잘못된 일이 벌어졌다는 것을 바로 알 수 있습니다. 우리는 프로그램을 실행할 때 (언어의 특성에 따라서는 컴파일하는 동안에) 임의의 값을 주행거리계에 대입할 수 없다는 메시지를 받게 됩니다. 정확히 동일한 메시지는 아니어도, 최소한 그와 어느정도 비슷한 메시지를 받을 수는 있을겁니다. 그것이 에러를 방지하지는 못합니다. 그렇지요? 하지만 그로 인해 에러의 원인이 무엇인지를 빨리 알 수 있습니다. 이것이 OO 프로그래밍이 시간 낭비를 줄여주는 수많은 경우 중 한 가지입니다.

보통 이 수준보다 한 단계 더 추상화를 진행합니다. 왜냐하면 개별 기계를 만드는 것 만큼 기계를 만드는 공장을 만드는 것도 쉬운 것으로 드러났기 때문입니다. 보통 주행 거리계를 한 개만 만드는 일은 없을 것입니다. 대신 한 가지 패턴으로 여러 주행 거리계를 제작할 수 있도록 준비할 것입니다. 패턴(혹은 주행 거리계 공장)은 우리가 클래스(class)라 부르는 것에 해당하고, 그 패턴으로 제작되는 (혹은 그 공장에서 생산되는) 개별 주행 거리계는 객체(object)에 해당합니다. 대부분의 OO언어에서는 개별 객체를 생성하기 전에 클래스를 정의할 필요가 있지만, 루비는 그렇지 않습니다.

여기서 객체지향 언어를 사용하는 것이 좋은 객체지향 디자인을 하도록 강제로 만들수는 없다는 것을 밝힐 가치가 있을겁니다. 실제로 어떤 언어로든 불명확하고, 너저분하며, 부주의하고, 버그 투성이의 불안정한 프로그램을 짤 수 있습니다. 루비가 제공하는 것(특히 C++과 대비했을 때)은 객체지향 프로그래밍의 기법들이 자연스럽게 느껴질 수 있도록 해서, 아주 작은 프로그램을 작성할 때 마저도 노력을 줄이기 위해 나쁜 기법의 코드를 작성할 필요를 느낄 수 없게 하는 것입니다. 우리는 루비가 이런 경탄할만한 목적을 어떻게 달성하는지에 대해 이 가이드를 진행해 나가면서 살펴볼 예정입니다. 다음에 다룰 주제는 "스위치와 다이얼"(객체의 메소드)이고, 그로부터 "공장" (클래스)으로 진행해 나갈 것입니다. 여러분, 이 여행을 계속 함께 하십시다.


Trackbacks 1 : Comments 0