펄 객체지향 프로그래밍 튜토리얼
펄 객체지향 프로그래밍 튜토리얼 (perlootut)
Perl에서 객체지향 프로그래밍(OOP)을 시작하기 위한 튜토리얼이에요. 객체지향 설계의 기본 개념부터, Perl이 제공하는 기능 위에 쌓아 올린 CPAN의 여러 OO 시스템까지 차근차근 소개해요.
출처: perlootut - Object-Oriented Programming in Perl Tutorial
날짜 (DATE)
이 문서는 2011년 2월에 만들어졌고, 마지막 주요 개정은 2013년 2월이었어요.
지금 이것을 읽고 계신다면 기술의 최신 상태가 바뀌었을 가능성이 있어요. 이 버전보다는 최신 안정 릴리스의 Perl에 있는 perlootut 문서부터 읽기 시작하시길 권해요.
개요 (DESCRIPTION)
이 문서는 Perl의 객체지향 프로그래밍에 대한 소개를 제공해요. 객체지향 설계 뒤에 있는 개념의 간략한 개요로 시작한 다음, Perl이 제공하는 것 위에 구축된 CPAN의 여러 OO 시스템을 소개해요.
기본적으로 Perl의 내장 OO 시스템은 아주 최소한이라, 대부분의 작업을 여러분이 해야 해요. 이런 최소함은 1994년에는 아주 이치에 맞았지만, Perl 5.0 이후 수년간 Perl OO에서 많은 공통 패턴이 등장하는 것을 봤어요. 다행히 Perl의 유연성 덕분에 풍부한 Perl OO 시스템 생태계가 번성할 수 있었어요.
Perl OO가 내부적으로 어떻게 동작하는지 알고 싶다면, perlobj 문서가 세부적인 내용을 설명해요.
이 문서는 이미 Perl 문법, 변수 타입, 연산자, 서브루틴 호출의 기본을 이해하고 있다고 가정해요. 아직 이해하지 못했다면 먼저 perlintro를 읽어주세요. perlsyn, perlop, perlsub 문서도 읽어야 해요.
객체지향 기초 (OBJECT-ORIENTED FUNDAMENTALS)
대부분의 객체 시스템은 여러 공통 개념을 공유해요. "클래스(class)", "객체(object)", "메서드(method)", "속성(attribute)" 같은 용어를 들어본 적이 있을 거예요. 이 개념을 이해하면 객체지향 코드를 읽고 쓰기가 훨씬 쉬워져요. 이미 익숙하다면 이 섹션을 훑어보기만 해도 좋아요. 각 개념을 Perl의 OO 구현 관점에서 설명하니까요.
Perl의 OO 시스템은 클래스 기반이에요. 클래스 기반 OO는 꽤 흔하며, Java, C++, C#, Python, Ruby 등 많은 언어가 사용해요. 다른 객체지향 패러다임도 있어요. JavaScript가 다른 패러다임을 쓰는 가장 인기 있는 언어로, JavaScript의 OO 시스템은 프로토타입 기반이에요.
객체 (Object)
객체(object) 는 데이터와 그 데이터를 다루는 서브루틴을 함께 묶은 데이터 구조예요. 객체의 데이터를 속성(attributes) 이라 하고, 서브루틴을 메서드(methods) 라고 해요. 객체는 명사(사람, 웹 서비스, 컴퓨터)로 생각할 수 있어요.
객체는 하나의 개별적인 것을 나타내요. 예를 들어 객체가 파일 하나를 나타낼 수 있어요. 파일 객체의 속성에는 경로(path), 내용(content), 마지막 수정 시간이 포함될 수 있어요. "foo.example.com"이라는 머신의 /etc/hostname을 나타내는 객체를 만든다면, 그 객체의 경로는 "/etc/hostname", 내용은 "foo\n", 마지막 수정 시간은 에포크(epoch) 시작 이후 1304974868초가 될 거예요.
파일과 연관된 메서드에는 rename()과 write()가 있을 수 있어요.
Perl에서 대부분의 객체는 해시지만, 우리가 권장하는 OO 시스템은 이런 것을 신경 쓸 필요 없게 해줘요. 실제로는 객체의 내부 데이터 구조를 불투명한 것으로 보는 것이 가장 좋아요.
클래스 (Class)
클래스(class) 는 객체의 한 범주(category)의 동작을 정의해요. 클래스는 범주의 이름("File"처럼)이고, 그 범주에 속한 객체의 동작도 정의해요.
모든 객체는 특정 클래스에 속해요. 예를 들어 우리의 /etc/hostname 객체는 File 클래스에 속해요. 특정 객체를 만들고 싶을 때는 클래스에서 시작해 객체를 구성(construct) 하거나 인스턴스화(instantiate) 해요. 특정 객체는 종종 클래스의 인스턴스(instance) 라고 불려요.
Perl에서 어떤 패키지든 클래스가 될 수 있어요. 클래스인 패키지와 아닌 패키지의 차이는 패키지가 어떻게 사용되는지에 기반해요. File 클래스의 "클래스 선언"은 이렇습니다:
package File;
Perl에는 객체를 구성하기 위한 특별한 키워드가 없어요. 하지만 CPAN의 대부분 OO 모듈은 new()라는 메서드를 사용해 새 객체를 구성해요:
my $hostname = File->new(
path => '/etc/hostname',
content => "foo\n",
last_mod_time => 1304974868,
);
(그 -> 연산자에 대해 걱정하지 마세요, 나중에 설명할 거예요.)
블레싱 (Blessing)
앞서 말했듯이 대부분의 Perl 객체는 해시지만, 객체는 어떤 Perl 데이터 타입(스칼라, 배열 등)의 인스턴스일 수 있어요. 평범한 데이터 구조를 객체로 바꾸는 것은 Perl의 bless 함수를 사용해 그 데이터 구조를 블레싱(blessing) 함으로써 이루어져요.
객체를 처음부터 직접 만들지 않기를 강력히 권하지만, bless라는 용어는 알아야 해요. bless된 데이터 구조(일명 "referent")는 객체예요. 때로 객체가 "클래스에 블레싱되었다"고 말해요.
referent가 블레싱되면, 코어 모듈 Scalar::Util의 blessed 함수가 클래스 이름을 알려줄 수 있어요. 이 서브루틴은 객체를 전달하면 그 객체의 클래스를 반환하고, 그렇지 않으면 false를 반환해요.
use Scalar::Util 'blessed';
print blessed($hash); # undef
print blessed($hostname); # File
생성자 (Constructor)
생성자(constructor) 는 새 객체를 만들어요. Perl에서 클래스의 생성자는 그저 또 다른 메서드예요. 생성자에 대한 문법을 제공하는 일부 언어들과 달리 말이죠. 대부분의 Perl 클래스는 생성자 이름으로 new를 사용해요:
my $file = File->new(...);
메서드 (Methods)
메서드(method) 가 객체에 대해 동작하는 서브루틴이라는 것을 이미 배웠어요. 메서드를 객체가 할 수 있는 것들로 생각할 수 있어요. 객체가 명사라면 메서드는 동사(저장, 출력, 열기)예요.
Perl에서 메서드는 단순히 클래스의 패키지에 사는 서브루틴이에요. 메서드는 항상 객체를 첫 번째 인자로 받도록 작성돼요:
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
$file->print_info;
# The file is at /etc/hostname
메서드를 특별하게 만드는 것은 호출되는 방식이에요. 화살표 연산자(->)는 Perl에게 우리가 메서드를 호출하고 있다고 알려줘요.
메서드 호출을 하면, Perl은 메서드의 invocant가 첫 번째 인자로 전달되도록 조정해요. Invocant는 화살표 왼쪽에 있는 것을 가리키는 멋진 이름이에요. invocant는 클래스 이름이나 객체일 수 있어요. 메서드에 추가 인자를 전달할 수도 있어요:
sub print_info {
my $self = shift;
my $prefix = shift // "This file is at ";
print $prefix, ", ", $self->path, "\n";
}
$file->print_info("The file is located at ");
# The file is located at /etc/hostname
속성 (Attributes)
각 클래스는 속성(attributes) 을 정의할 수 있어요. 객체를 인스턴스화할 때 그 속성에 값을 할당해요. 예를 들어 모든 File 객체는 경로를 가져요. 속성은 때로 프로퍼티(properties) 라고도 불려요.
Perl에는 속성에 대한 특별한 문법이 없어요. 내부적으로 속성은 종종 객체의 기본 해시의 키로 저장되지만, 이것에 대해 걱정하지 마세요.
접근자(accessor) 메서드를 통해서만 속성에 접근하는 걸 권장해요. 접근자는 각 속성의 값을 얻거나 설정할 수 있는 메서드예요. 앞서 print_info() 예제에서 $self->path를 호출하는 것을 봤죠.
getter와 setter라는 용어도 볼 수 있어요. 이 둘은 접근자의 두 종류예요. getter는 속성 값을 얻고, setter는 값을 설정해요. setter의 또 다른 용어는 mutator예요.
속성은 일반적으로 읽기 전용(read-only) 또는 읽기-쓰기(read-write)로 정의돼요. 읽기 전용 속성은 객체가 처음 생성될 때만 설정할 수 있고, 읽기-쓰기 속성은 언제든 바꿀 수 있어요.
속성의 값 자체가 다른 객체일 수도 있어요. 예를 들어 File 클래스는 마지막 수정 시간을 숫자로 반환하는 대신, 그 값을 나타내는 DateTime 객체를 반환할 수 있어요.
공개적으로 설정 가능한 속성을 전혀 노출하지 않는 클래스도 있을 수 있어요. 모든 클래스가 속성과 메서드를 가지는 건 아니에요.
다형성 (Polymorphism)
다형성(polymorphism) 은 서로 다른 두 클래스의 객체가 API를 공유한다는 것을 뜻하는 멋진 표현이에요. 예를 들어 File과 WebPage 클래스가 둘 다 print_content() 메서드를 가질 수 있어요. 이 메서드는 각 클래스에 따라 다른 출력을 만들 수 있지만, 공통 인터페이스를 공유해요.
두 클래스는 여러 면에서 다를 수 있지만, print_content() 메서드에 관해서는 같아요. 이것은 어느 클래스의 객체에서든 print_content() 메서드를 호출해 볼 수 있고, 객체가 어떤 클래스에 속하는지 알 필요가 없다는 뜻이에요!
다형성은 객체지향 설계의 핵심 개념 중 하나예요.
상속 (Inheritance)
상속(inheritance) 은 기존 클래스의 특수화된 버전을 만들 수 있게 해줘요. 상속은 새 클래스가 다른 클래스의 메서드와 속성을 재사용할 수 있게 해요.
예를 들어 File에서 상속하는 File::MP3 클래스를 만들 수 있어요. File::MP3는 File의 더 특수한 타입이에요(is-a). 모든 mp3 파일은 파일이지만, 모든 파일이 mp3 파일은 아니에요.
상속 관계를 부모-자식 또는 superclass/subclass 관계라고 부르는 경우가 많아요. 때로 자식이 부모 클래스와 is-a 관계를 가진다고 말해요.
File은 File::MP3의 슈퍼클래스(superclass) 이고, File::MP3는 File의 서브클래스(subclass) 예요.
package File::MP3;
use parent 'File';
parent 모듈은 Perl이 상속 관계를 정의할 수 있게 해주는 여러 방법 중 하나예요.
Perl은 다중 상속을 허용해요. 즉 클래스가 여러 부모로부터 상속할 수 있다는 뜻이에요. 가능하지만 강력히 반대해요. 일반적으로 역할(roles) 을 사용하면 다중 상속으로 할 수 있는 모든 일을 더 깔끔하게 할 수 있어요.
주어진 클래스의 서브클래스를 여러 개 정의하는 것 자체에는 문제가 없다는 점을 주의하세요. 이것은 흔하고 안전해요. 예를 들어 서로 다른 타입의 mp3 파일을 구분하기 위해 File::MP3::FixedBitrate와 File::MP3::VariableBitrate 클래스를 정의할 수 있어요.
메서드 오버라이딩과 메서드 해석 (Overriding methods and method resolution)
상속은 두 클래스가 코드를 공유할 수 있게 해줘요. 기본적으로 부모 클래스의 모든 메서드는 자식에서도 사용할 수 있어요. 자식은 자신의 구현을 제공하기 위해 부모의 메서드를 명시적으로 오버라이드(override) 할 수 있어요. 예를 들어 File::MP3 객체가 있다면, File의 print_info() 메서드를 가져요:
my $cage = File::MP3->new(
path => 'mp3s/My-Body-Is-a-Cage.mp3',
content => $mp3_data,
last_mod_time => 1304974868,
title => 'My Body Is a Cage',
);
$cage->print_info;
# The file is at mp3s/My-Body-Is-a-Cage.mp3
인사말에 mp3의 제목을 포함하고 싶다면 메서드를 오버라이드할 수 있어요:
package File::MP3;
use parent 'File';
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
print "Its title is ", $self->title, "\n";
}
$cage->print_info;
# The file is at mp3s/My-Body-Is-a-Cage.mp3
# Its title is My Body Is a Cage
어떤 메서드를 사용해야 할지 결정하는 과정을 메서드 해석(method resolution) 이라고 해요. Perl이 하는 일은 먼저 객체의 클래스(이 경우 File::MP3)를 보는 거예요. 그 클래스가 메서드를 정의하면 그 클래스 버전의 메서드가 호출돼요. 그렇지 않으면 Perl은 각 부모 클래스를 차례로 봐요. File::MP3의 유일한 부모는 File이에요. File::MP3가 메서드를 정의하지 않았지만 File이 정의한다면, Perl은 File의 메서드를 호출해요.
File이 DataSource에서 상속하고, DataSource가 Thing에서 상속한다면, 필요할 때 Perl은 "체인을 따라 위로" 계속 찾아볼 거예요.
자식에서 부모 메서드를 명시적으로 호출하는 것도 가능해요:
package File::MP3;
use parent 'File';
sub print_info {
my $self = shift;
$self->SUPER::print_info();
print "Its title is ", $self->title, "\n";
}
SUPER:: 부분은 Perl에게 File::MP3 클래스의 상속 체인에서 print_info()를 찾으라고 알려줘요. 이 메서드를 구현하는 부모 클래스를 찾으면 그 메서드가 호출돼요.
앞서 다중 상속을 언급했죠. 다중 상속의 주요 문제는 메서드 해석을 크게 복잡하게 한다는 거예요. 자세한 내용은 perlobj를 보세요.
캡슐화 (Encapsulation)
캡슐화(encapsulation) 는 객체가 불투명하다는 생각이에요. 다른 개발자가 여러분의 클래스를 사용할 때, 어떻게 구현되었는지 알 필요 없고 무엇을 하는지만 알면 돼요.
캡슐화는 여러 이유로 중요해요. 첫째, 공개 API와 사적인 구현을 분리할 수 있게 해줘요. 즉 API를 깨지 않으면서 구현을 바꿀 수 있다는 뜻이에요.
둘째, 클래스가 잘 캡슐화되면 서브클래싱이 더 쉬워져요. 이상적으로 서브클래스는 부모 클래스가 사용하는 것과 같은 API를 사용해 객체 데이터에 접근해요. 현실에서는 서브클래싱이 때로 캡슐화를 위반하는 것을 포함하지만, 좋은 API는 그럴 필요를 최소화할 수 있어요.
앞서 대부분의 Perl 객체가 내부적으로 해시로 구현된다고 언급했어요. 캡슐화의 원칙은 우리가 이것에 의존해서는 안 된다고 말해줘요. 대신 접근자 메서드를 사용해 그 해시의 데이터에 접근해야 해요. 아래에서 권장하는 객체 시스템은 모두 접근자 메서드 생성을 자동화해요. 그중 하나를 사용하면 객체를 해시로 직접 접근할 필요가 전혀 없어요.
컴포지션 (Composition)
객체지향 코드에서 한 객체가 다른 객체를 참조하는 경우가 자주 있어요. 이를 컴포지션(composition) 또는 has-a 관계라고 불러요.
앞서 File 클래스의 last_mod_time 접근자가 DateTime 객체를 반환할 수 있다고 언급했어요. 이것은 컴포지션의 완벽한 예시예요. 더 나아가 path와 content 접근자도 객체를 반환하게 할 수 있어요. 그러면 File 클래스는 여러 다른 객체로 구성(composed) 될 거예요.
역할 (Roles)
역할(roles) 은 클래스가 무엇인지(is) 가 아니라 무엇을 하는지(does) 에 관한 것이에요. 역할은 Perl에서 비교적 새롭지만 꽤 인기를 얻었어요. 역할은 클래스에 적용(applied) 돼요. 때로 클래스가 역할을 소비(consume) 한다고 말해요.
역할은 다형성을 제공하는 상속의 대안이에요. Radio와 Computer 두 클래스가 있다고 가정해보죠. 둘 다 켜기/끄기 스위치가 있어요. 이것을 클래스 정의에 모델링하고 싶어요.
두 클래스 모두 Machine 같은 공통 부모에서 상속하게 할 수도 있지만, 모든 기계에 켜기/끄기 스위치가 있는 건 아니에요. HasOnOffSwitch라는 부모 클래스를 만들 수도 있지만, 그것은 아주 인위적이에요. 라디오와 컴퓨터는 이 부모의 특수화가 아니에요. 이 부모는 정말 우스꽝스러운 창조물이에요.
바로 여기에 역할이 등장해요. HasOnOffSwitch 역할을 만들어 두 클래스에 적용하는 것은 아주 이치에 맞아요. 이 역할은 turn_on()과 turn_off() 메서드를 제공하는 것 같은 알려진 API를 정의해요.
Perl에는 역할을 표현하는 내장 방법이 없어요. 과거에는 사람들이 이를 감수하고 다중 상속을 사용했어요. 요즘은 역할 사용을 위한 CPAN의 좋은 선택지가 여러 개 있어요.
OO를 언제 써야 하나 (When to Use OO)
객체지향이 모든 문제에 최선의 해결책은 아니에요. Perl Best Practices(2004, O'Reilly Media 출판)에서 Damian Conway는 OO가 여러분의 문제에 적합한지 결정할 때 사용할 기준 목록을 제공해요:
-
설계하는 시스템이 크거나, 커질 가능성이 있다.
-
데이터가 명확한 구조로 집계될 수 있고, 특히 각 집계체 안에 많은 양의 데이터가 있다.
-
다양한 데이터 집계 타입이 상속과 다형성의 사용을 촉진하는 자연스러운 계층을 형성한다.
-
많은 다른 연산이 적용되는 데이터 조각이 있다.
-
관련된 타입의 데이터에 동일한 일반 연산을 수행해야 하지만, 연산이 적용되는 특정 데이터 타입에 따라 약간의 변형이 있다.
-
나중에 새 데이터 타입을 추가해야 할 가능성이 있다.
-
데이터 조각 사이의 전형적인 상호작용이 연산자로 가장 잘 표현된다.
-
시스템의 개별 구성요소 구현이 시간에 따라 바뀔 가능성이 있다.
-
시스템 설계가 이미 객체지향적이다.
-
많은 수의 다른 프로그래머가 여러분의 코드 모듈을 사용할 것이다.
PERL OO 시스템 (PERL OO SYSTEMS)
앞서 언급했듯이 Perl의 내장 OO 시스템은 매우 최소하지만 꽤 유연해요. 수년간 많은 사람들이 더 많은 기능과 편의를 제공하기 위해 Perl의 내장 시스템 위에 구축된 시스템을 개발했어요.
이 시스템 중 하나를 사용하길 강력히 권장해요. 가장 최소한의 것조차도 많은 반복적인 보일러플레이트를 제거해줘요. Perl에서 클래스를 처음부터 직접 작성할 타당한 이유가 정말 없어요.
이 시스템의 내부에 관심이 있다면 perlobj를 확인해보세요.
Moose
Moose는 스스로를 "Perl 5를 위한 포스트모던 객체 시스템"이라고 소개해요. 겁먹지 마세요. "포스트모던"이라는 라벨은 Larry가 Perl을 "최초의 포스트모던 컴퓨터 언어"라고 묘사한 것에 대한 콜백이에요.
Moose는 완전하고 현대적인 OO 시스템을 제공해요. 가장 큰 영향은 Common Lisp Object System이지만, Smalltalk과 여러 다른 언어의 아이디어도 빌려와요. Moose는 Stevan Little이 만들었고, Raku OO 설계에 대한 그의 작업에서 크게 끌어왔어요.
Moose를 사용한 우리의 File 클래스는 이렇습니다:
package File;
use Moose;
has path => ( is => 'ro' );
has content => ( is => 'ro' );
has last_mod_time => ( is => 'ro' );
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
Moose는 여러 기능을 제공해요:
- 선언적 슈거 (Declarative sugar)
Moose는 클래스를 정의하기 위한 선언적 "슈거" 계층을 제공해요. 그 슈거는 단지 여러분의 클래스가 어떻게 동작하는지 선언하는 것을 더 단순하고 매력적으로 만들어 주는 내보낸 함수들의 집합이에요. 이것은 Perl에게 클래스를 어떻게 구현할지 말하는 대신, 여러분의 클래스가 무엇인지 설명할 수 있게 해줘요.
has() 서브루틴은 속성을 선언하고, Moose는 이 속성들에 대한 접근자를 자동으로 만들어요. 또한 new() 메서드를 만들어 주는 일도 처리해요. 이 생성자는 여러분이 선언한 속성을 알기 때문에, 새 File을 만들 때 그 속성들을 설정할 수 있어요.
- 내장 역할 (Roles built-in)
Moose는 클래스를 정의하는 것과 같은 방식으로 역할을 정의할 수 있게 해줘요:
package HasOnOffSwitch;
use Moose::Role;
has is_on => (
is => 'rw',
isa => 'Bool',
);
sub turn_on {
my $self = shift;
$self->is_on(1);
}
sub turn_off {
my $self = shift;
$self->is_on(0);
}
- 미니어처 타입 시스템 (A miniature type system)
위 예제에서 is_on 속성을 만들 때 has()에 isa => 'Bool'을 전달한 것을 볼 수 있어요. 이것은 Moose에게 이 속성이 불리언 값이어야 한다고 알려줘요. 잘못된 값으로 설정하려고 하면 코드가 에러를 던질 거예요.
- 완전한 내부조사와 조작 (Full introspection and manipulation)
Perl의 내장 내부조사(introspection) 기능은 꽤 최소해요. Moose는 그 위에 구축해 클래스에 대한 완전한 내부조사 계층을 만들어요. 이것은 "File 클래스가 어떤 메서드를 구현할까?" 같은 질문을 할 수 있게 해주고, 프로그래매틱하게 클래스를 수정할 수도 있게 해줘요.
- 자기 호스팅과 확장 가능 (Self-hosted and extensible)
Moose는 자신의 내부조사 API를 사용해 자신을 설명해요. 멋진 트릭일 뿐 아니라, Moose를 사용해 Moose 자체를 확장할 수 있다는 뜻이에요.
- 풍부한 생태계 (Rich ecosystem)
CPAN의 MooseX 네임스페이스 아래 풍부한 Moose 확장 생태계가 있어요. 게다가 CPAN의 많은 모듈이 이미 Moose를 사용하므로, 배울 수 있는 예제가 많아요.
- 더 많은 기능 (Many more features)
Moose는 매우 강력한 도구이고 여기서 모든 기능을 다룰 수는 없어요. Moose::Manual에서 시작해 Moose 문서를 읽어 더 배우시길 권해요.
물론 Moose가 완벽하지는 않아요.
Moose는 코드 로딩을 더 느리게 만들 수 있어요. Moose 자체는 작지 않고, 클래스를 정의할 때 많은 코드 생성(code generation)을 해요. 이 코드 생성은 런타임 코드가 가능한 한 빠르게 동작하게 하지만, 모듈이 처음 로드될 때 그 비용을 치러요.
이 로드 시간 비용은 명령줄 스크립트나 실행될 때마다 로드해야 하는 "순수" CGI 스크립트처럼 시작 속도가 중요할 때 문제가 될 수 있어요.
당황하기 전에, 많은 사람들이 명령줄 도구와 다른 시작에 민감한 코드에 Moose를 사용한다는 것을 알아두세요. 시작 속도를 걱정하기 전에 먼저 Moose를 시도해 보시길 권해요.
Moose는 또한 다른 모듈에 대한 여러 의존성이 있어요. 대부분은 작은 독립 모듈이고, 그중 다수가 Moose에서 분리되었어요. Moose 자체와 일부 의존성은 컴파일러를 요구해요. 컴파일러가 없는 시스템에 소프트웨어를 설치해야 하거나, 어떤 의존성이라도 있는 것이 문제라면 Moose는 적합하지 않을 수 있어요.
Moo
Moose를 시도해 보고 이 문제 중 하나가 Moose 사용을 막는다면, 그다음에 Moo를 고려하시길 권해요. Moo는 더 단순한 패키지로 Moose 기능의 부분집합을 구현해요. 구현하는 대부분의 기능에 대해 최종 사용자 API는 Moose와 동일해서, Moo에서 Moose로 꽤 쉽게 전환할 수 있어요.
Moo는 Moose의 내부조사 API 대부분을 구현하지 않기 때문에, 모듈을 로드할 때 더 빠른 경우가 많아요. 또한 의존성 중 어떤 것도 XS를 요구하지 않아서 컴파일러가 없는 머신에도 설치할 수 있어요.
Moo의 가장 매력적인 기능 중 하나는 Moose와의 상호운용성이에요. 누군가 Moo 클래스나 역할에 Moose의 내부조사 API를 사용하려 하면, 투명하게 Moose 클래스나 역할로 인플레이트(inflate)돼요. 이것은 Moo를 사용하는 코드를 Moose 코드 베이스에 통합하고 그 반대도 쉽게 만들어요.
예를 들어 Moose 클래스는 extends를 사용해 Moo 클래스를 서브클래싱하거나, with를 사용해 Moo 역할을 소비할 수 있어요.
Moose 저자들은 언젠가 Moose를 충분히 개선해 Moo를 쓸모없게 만들 수 있기를 바라지만, 지금으로서는 Moose에 대한 가치 있는 대안을 제공해요.
Class::Accessor
Class::Accessor는 Moose의 정반대예요. 기능을 거의 제공하지 않고 자기 호스팅도 아니에요.
하지만 아주 단순하고, 순수 Perl이며, 비코어 의존성이 없어요. 또한 지원하는 기능에 대해 요구에 따라 "Moose-유사" API를 제공해요.
별로 하는 일이 없어도, 클래스를 처음부터 직접 작성하는 것보다는 여전히 선호돼요.
Class::Accessor를 사용한 File 클래스:
package File;
use Class::Accessor 'antlers';
has path => ( is => 'ro' );
has content => ( is => 'ro' );
has last_mod_time => ( is => 'ro' );
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
antlers import 플래그는 Class::Accessor에게 Moose-유사 문법으로 속성을 정의하고 싶다고 알려줘요. has에 전달할 수 있는 유일한 매개변수는 is예요. Class::Accessor를 선택한다면 이 Moose-유사 문법을 사용하길 권해요. 나중에 Moose로 옮기기로 결정하면 더 매끄러운 업그레이드 경로가 되기 때문이에요.
Moose처럼 Class::Accessor는 클래스에 대한 접근자 메서드와 생성자를 생성해요.
Class::Tiny
마지막으로 Class::Tiny가 있어요. 이 모듈은 정말 이름 그대로예요. 믿을 수 없을 정도로 최소한의 API와, 최근 Perl에 대한 의존성이 전혀 없어요. 그래도 순수하게 직접 OO 코드를 작성하는 것보다 훨씬 사용하기 쉽다고 생각해요.
File 클래스를 다시 한 번 보죠:
package File;
use Class::Tiny qw( path content last_mod_time );
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
그게 다예요!
Class::Tiny에서는 모든 접근자가 읽기-쓰기예요. 여러분이 정의한 접근자뿐 아니라 생성자도 생성해줘요.
Moose-유사 문법을 위해 Class::Tiny::Antlers를 사용할 수도 있어요.
Role::Tiny
앞서 언급했듯이 역할은 상속의 대안을 제공하지만, Perl에는 내장 역할 지원이 없어요. Moose를 선택한다면 완전한 역할 구현이 함께 제공돼요. 하지만 다른 추천 OO 모듈 중 하나를 사용한다면 Role::Tiny로 여전히 역할을 사용할 수 있어요.
Role::Tiny는 Moose의 역할 시스템과 같은 기능 중 일부를 훨씬 작은 패키지로 제공해요. 가장 주목할 만한 점은 어떤 종류의 속성 선언도 지원하지 않아서 직접 해야 한다는 거예요. 그래도 유용하고, Class::Accessor와 Class::Tiny와 잘 동작해요.
OO 시스템 요약 (OO System Summary)
우리가 다룬 옵션의 간단한 요약이에요:
Moose
Moose는 최대 옵션이에요. 많은 기능, 큰 생태계, 그리고 활발한 사용자 기반을 가져요. Moo도 간단히 다뤘죠. Moo는 Moose 라이트이며, Moose가 여러분의 애플리케이션에 맞지 않을 때 합리적인 대안이에요.
Class::Accessor
Class::Accessor는 Moose보다 훨씬 적게 하고, Moose가 부담스럽다면 좋은 대안이에요. 오래되었고 잘 검증되었어요. 또한 Class::Accessor에서 Moose로의 이동을 쉽게 해주는 최소한의 Moose 호환 모드가 있어요.
Class::Tiny
Class::Tiny는 절대적인 최소 옵션이에요. 의존성이 없고 배울 문법이 거의 없어요. 초최소 환경과 세부사항을 걱정하지 않고 빠르게 뭔가를 조립할 때 좋은 옵션이에요.
Role::Tiny
다중 상속을 고려하고 있다면 Class::Accessor나 Class::Tiny와 함께 Role::Tiny를 사용하세요. Moose로 간다면 자체 역할 구현이 함께 제공돼요.
다른 OO 시스템 (Other OO Systems)
여기서 다룬 것 외에도 CPAN에는 말 그대로 수십 개의 다른 OO 관련 모듈이 있고, 다른 사람의 코드로 작업하면 그중 하나 이상을 만나게 될 가능성이 있어요.
게다가 야생에는 Perl 내장 OO 기능만 사용해 모든 OO를 "손으로" 하는 코드도 많아요. 그런 코드를 유지해야 한다면, Perl의 내장 OO가 정확히 어떻게 동작하는지 이해하기 위해 perlobj를 읽어야 해요.
결론 (CONCLUSION)
앞서 말했듯이 Perl의 최소한의 OO 시스템은 CPAN에서 OO 시스템이 쏟아져 나오게 했어요. 여전히 바닥까지 내려가 손으로 클래스를 쓸 수 있지만, 현대 Perl에서 그렇게 할 이유는 정말 없어요.
작은 시스템에는 Class::Tiny와 Class::Accessor가 모두 기본 보일러플레이트를 처리해 주는 최소한의 객체 시스템을 제공해요.
더 큰 프로젝트에는 Moose가 비즈니스 로직 구현에 집중할 수 있게 해주는 풍부한 기능 집합을 제공해요. Moo는 많은 기능을 원하지만 더 빠른 컴파일 시간이나 XS 회피가 필요할 때 Moose에 대한 훌륭한 대안을 제공해요.
Moose, Moo, Class::Accessor, Class::Tiny를 가지고 놀고 평가해서 어떤 OO 시스템이 여러분에게 맞는지 확인하시길 권해요.
더 알아보기 (Learn more)
perlobj— Perl 내장 OO의 내부 동작perlintro— Perl 입문(기초 전제)perlsub— 서브루틴Scalar::Util—blessed함수Moose,Moo,Class::Accessor,Class::Tiny,Role::Tiny— CPAN OO 시스템