클래스 만들기

클래스 만들기

클래스(class)는 Racket 객체 시스템의 핵심이에요. class 형식으로 필드·이니셜라이저·메서드·상속·슈퍼클래스 호출 등을 선언할 수 있어요. 《Racket 가이드》의 [Classes and Objects] 절이 이 개념을 소개해요.

이 절에서는 class, class*, class/derived 형식과 클래스 선언 문법을 다뤄요.

출처: Racket Reference

본문

syntax class

(class superclass-expr class-clause ...)

superclass-expr이 만들어내는 클래스의 서브클래스인 클래스를 만들어요. 클래스는 프로시저와 같은 일급 값이에요. 클래스를 평가하면 새 클래스 객체가 만들어져요.

class-clause들은 클래스의 필드, 메서드, 상속 규칙을 선언해요. 주요 절들은 다음과 같아요.

  • (init name-expr ...) — 클래스 생성 시 인자로 받는 이니셜라이저들을 선언해요. (init [name-expr default-expr] ...) 형태로 기본값을 줄 수도 있어요.
  • (init-field name-expr ...)init으로 값이 채워지고 동시에 필드가 되는 선언이에요.
  • (field name-expr ...) — 일반 필드를 선언해요.
  • (public name-expr ...) — 공개 메서드를 선언해요. (public (name-expr . formals) body ...+) 형태로 메서드 정의를 함께 줄 수 있어요.
  • (private name-expr ...) — 비공개 메서드를 선언해요.
  • (inherit name-expr ...) — 슈퍼클래스의 메서드를 현재 클래스에서 사용할 수 있게 해요.
  • (super name-expr ...) — 슈퍼클래스의 메서드를 super로 호출할 수 있게 해요.
  • (override name-expr ...) — 슈퍼클래스의 메서드를 오버라이드해요.
  • (augment name-expr ...) — 슈퍼클래스에서 호출되는 확장이 가능한(augmentable) 메서드를 정의해요.
  • (inner name-expr ...) — 서브클래스가 추가로 구현할 수 있는 "내부(inner)" 메서드를 선언해요.
  • (sequence name-expr ...) — 필드 초기화 순서를 제어해요.
  • (derived id) — 클래스 이름을 지정해요.
  • (method name-expr ...)class*와 함께 쓰는 방법이에요.

공개 메서드 선언의 예:

(define breakfast%
  (class object%
    (init size)
    (init-field kind)
    (field (milk 0))
    (define current-size size)     ; 로컬 정의 가능
    (public (eat amount)
      (set! current-size (- current-size amount)))
    (super-new)))

super-new는 슈퍼클래스의 이니셜라이저를 호출하는 특별한 형식이에요. (super-new [name-expr val-expr] ...) 형태로 슈퍼클래스 init 인자를 넘길 수 있어요. 모든 클래스 본문은 반드시 super-new를 (직접 또는 간접적으로) 호출해야 해요.

메서드와 필드 접근:

  • 메서드 안에서 속한 객체의 메서드를 호출할 때는 (send this method-name arg ...) 형태를 쓰거나, public 메서드라면 직접 (method-name arg ...)로 호출할 수 있어요.
  • field로 선언한 필드는 this를 통해 접근하기보다, 메서드 안에서 직접 이름으로 참조해요.

syntax class*

(class* superclass-expr (interface-expr ...) class-clause ...)

class와 같지만, 두 번째 인자에 인터페이스/속성 표현식들이 추가로 와서 그 클래스가 어떤 인터페이스를 구현하는지 선언해요. interface-expr들은 인터페이스나 구조체 타입 속성 디스크립터이며, 주로 (class* object% (i<%>) ...)처럼 인터페이스를 구현할 때 쓰여요.

예시:

(define i<%> (interface () m))
(define c%
  (class* object% (i<%>)
    (public (m) 'implements-m)
    (super-new)))

syntax class/derived

(class/derived (id . rest-form) superclass-expr class-clause ...)

class와 같지만, (id . rest-form) 하위 형식이 구문 오류 보고를 위한 전체 형식으로 취급되고 그 외에는 무시돼요. 매크로가 class로 확장될 때 사용하도록 의도된 것이에요.

인터페이스와 클래스의 관계:

  • class로 만든 클래스는 여러 인터페이스를 구현할 수 있어요.
  • 어떤 클래스가 인터페이스를 구현하는지 확인하려면 implementation?·is-a? 등을 써요.

인스턴스 만들기와 메서드 호출:

(define b (new breakfast% [size 2] [kind 'oats]))
(send b eat 1)

new로 클래스의 인스턴스를 만들고, send로 메서드를 호출해요. 클래스와 객체 시스템 전반에 대한 자세한 내용은 《Racket 가이드》의 클래스 관련 절을 참고해요.

syntax send

(send obj-expr method-id arg ...)
(send obj-expr (method-id arg ...))

obj-expr의 결과인 객체에 method-id라는 메시지를 보내요. 즉 객체의 method-id 메서드를 주어진 인자들로 호출하고, 그 메서드의 결과를 돌려줘요. 그 이름을 가진 메서드가 없으면 오류가 발생해요.

procedure

(object? v) → boolean?
  • v : any/c

v가 객체이면 #t를 돌려줘요.

syntax make-object

(make-object class-expr [init-arg ...])

class-expr의 결과인 클래스의 인스턴스를 만들어요. init-arg들은 클래스의 초기 인자(init 인자)로 전달돼요.

syntax object

(object ([id init-expr] ...) ([id expr] ...) ...)

클래스 정의 없이 바로 객체를 만들어내는 형식이에요. [id init-expr] ...은 객체의 이니셜라이저이고, [id expr] ...은 객체의 메서드를 정의해요.

syntax class->interface

(class->interface id ...)

id라는 이름의 클래스가 구현하는 새 인터페이스를 만들어내요. 그 인터페이스는 해당 클래스가 제대로 제공하는 메서드들을 요구해요. 이렇게 만들어진 인터페이스는 그 클래스가 구현하는 여러 인터페이스의 공통 부분을 추출하는 데 유용해요.

더 알아보기