'2007/05'에 해당되는 글 6건

  1. 2007.05.24 [루비 사용자 가이드] 모듈
  2. 2007.05.13 [루비 사용자 가이드] 싱글턴 메소드
  3. 2007.05.07 [루비 사용자 가이드] 억세스 컨트롤
  4. 2007.05.06 [루비 사용자 가이드] 메소드 재정의
  5. 2007.05.06 [루비 사용자 가이드] 상속
  6. 2007.05.02 [루비 사용자 가이드] 클래스

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

루비/레일스 프로그래밍/루비 사용자 가이드 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와 같은 메소드를 사용할 수 있게 됩니다.

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

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 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) 언어라 불립니다.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 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로 변경할 수 있을겁니다). 이 예는 물론 유용성을 보여주기에는 너무 단순합니다. 억세스 컨트롤의 잇점은 더 복잡하고 재미있는 클래스를 만들 때 더 명확해 질 것입니다.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 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

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 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)이라고 부르기도 합니다. 이런 식으로 객체의 클래스를 정의할 수 있는 것은 객체지향의 중요한 잇점 중 하나입니다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 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에게 했던 것 같이 말이죠), 루비의 자동화된 가비지 컬렉션이 그 개가 필요 없는 버려진 개라고 생각하고, 사정없이 그 객체를 없애버립니다. 여러분도 아시겠지만, 원하는 개를 항상 만들 수 있기 때문에 실제로는 아무런 문제가 없습니다.


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

티스토리 툴바