package Person;# 클래스 이름을 패키지 이름으로 지정# Class for storing data about a personuse warnings;
use strict;
subnew{
my$class = shift; # 클래스 이름이 자동으로 첫번째 인자로 넘어옴my$self = {@_}; # new()의 인자로 해쉬 형태 리스트가 들어온 경우bless($self, $class);
# 해쉬 레퍼런스가 $class의 인스턴스임을 지정# $class 대신 명시적으로 "Person"이라고 하는 것은 좋지 않다.# Person의 서브클래스에 new()가 없을 경우 이곳의 new()가 불리기 때문return$self;
}
Perl의 서브루틴은 항상 어떤 값을 return하므로, setter도 뭔가 의미있는 값을 리턴하게 할 수 있다. 주로 다음 중에서 리턴함1
업데이트된 파라메터 (즉 호출할 때 넘겨준 인자 그대로)
subset_color{
my$self = shift;
$self->{Color} = shift;
}
# 이 경우 다음과 같이 여러 오브젝트에 반복 사용할 수 있다.$tv_horse->set_color( $eating->set_color( color_from_user() ));
업데이트되기 전의 기존 파라메터 값
subset_color{
my$self = shift;
my$old = $self->{Color};
$self->{Color} = shift;
$old;
}
# 기존의 값을 백업해 두기에 편하다.
{
my$old_color = $tv_horse->set_color('orange');
... do things with $tv_horse ...
$tv_horse->set_color($old_color);
}
void context에서 불릴 경우는 굳이 $old를 저장하여 반환하는 수고를 할 필요가 없으므로,
subset_color{
my$self = shift;
if (definedwantarray) {
# this method call is not in void context, so# the return value mattersmy$old = $self->{Color};
$self->{Color} = shift;
$old;
} else {
# this method call is in void context$self->{Color} = shift;
}
}
use Carp qw(croak);
subinstance_only{
ref(my$self = shift) orcroak"instance variable needed";
... use $self as the instance ...
}
subclass_only{
ref(my$class = shift) andcroak"class name needed";
... use $class as the class ...
}
Carp 모듈이 제공하는 croak()은 die()와 유사하고, carp()는 warn()과 유사하나, 에러가 발생한 지점이 아니라 그 메쏘드를 호출한 지점을 알려준다. 모듈을 만들때는 die나 warn대신에 Carp모듈을 사용할 것. (see /디버깅)
어떤 클래스->메쏘드를 불렀는데 그 클래스에 그 메쏘드가 없으면 @ISA에 나열된 클래스에서 메쏘드를 찾는다. 만일 슈퍼클래스에도 역시 @ISA가 있다면 재귀적으로 검사를 하게 된다. ISA 검사는 깊이 우선 탐색으로 이뤄짐.5
슈퍼클래스가 다른 파일에 있는 경우는 use아 @ISA를 따로 쓰지 않고 base 프라그마를 쓸 수도 있다.6
package Cow;use Animal;
use varsqw(@ISA); # 펄5.6 미만 버전을 고려해서 our를 쓰지 않고 패키지 변수 @ISA를 선언@ISA = qw(Animal);
# 위 대신 아래와 같이 가능package Cow;use base qw(Animal);
여러가지 세팅을 해야 한다면, 애초에 new는 단지 객체에 bless를 하여 반환하는 용도로 쓰고, 나머지 세팅은 별도의 메쏘드를 부르는 게 낫다. 그러면 다중 상속을 할 경우 어느 상위클래스의 생성자가 사용되든, 나머지 세팅은 각 상위클래스의 메쏘드를 각각 불러서 할 수 있다. (그런데 이것은 상위 클래스까지 내가 직접 만들었을 때 얘기고...)
어떤 메쏘드를 수행하려는데 상속 트리 내에 그 메쏘드가 없을 경우, Perl은 동일한 트리에서 AUTOLOAD라는 이름의 메쏘드를 찾아보고, 있으면 그 메쏘드를 실행한다.10
인자는 원래의 메쏘드에 넘어가는 인자를 그대로 사용.
원래 메쏘드의 이름은 fully qualified 형태로, AUTOLOAD메쏘드가 있는 패키지 내의 패키지 변수 $AUTOLOAD에 담김
복잡하지만 잘 사용되지 않는 메쏘드를, 미리 컴파일하지 않고 사용할 때 컴파일 하는 용도로 쓸 수 있다.11
## in AnimalsubAUTOLOAD{
our$AUTOLOAD;
(my$method = $AUTOLOAD) =~ s/.*:://s; # remove package nameif ($methodeq"eat") { # 원래 호출한 게 eat 메쏘드였다면## define eat: eat메쏘드를 이 지점에서 정의evalq{ sub eat { ... long definition goes here ...}
}; # End of eval's q{ } stringdie$@if$@; # if typo snuck ingoto&eat; # jump into it
} else { # unknown methodcroak"$_[0] does not know how to $method\n";
}
}
위의 예에서, 일단 AUTOLOAD가 수행되었으면 eat이 정의되었기 때문에 이후에는 바로 eat이 호출된다.
오토로딩을 끄거나 하는 작업을 쉽게 하려면 AutoLoader나 SelfLoader 모듈을 참고.
오브젝트의 레퍼런스 카운트가 0이 되어서 메모리에서 사라질 때, 해당 오브젝트에 대해 DESTROY 메쏘드가 자동으로 호출된다.14
A클래스의 오브젝트가 B클래스의 오브젝트 레퍼런스를 포함하고 있을때, A가 소멸하여 B 오브젝트의 레퍼런스 카운트가 0이 되어야 B도 소멸된다. B를 먼저 없애고 싶다면 A의 DESTROY 메쏘드에서 처리해 줄 필요가 있음.15
소멸자에는 항상 $self->SUPER::DESTROY 를 넣어서 슈퍼클래스의 소멸자를 불러주는 것이 좋다.16
# 아래와 같은 형태를
Class->class_method(@args);
$instance->instance_method(@other);
# 아래와 같이 쓸 수도 있다
class_method Class @args;
instance_method $instance@other;
# 대표적인 예 - 오브젝트 생성my$obj = new Some::Class @constructor_params;
Perl5 초기에 많이 사용된 형태이나, 쓰지 않는 것을 권장함:
오브젝트가 bareword이거나 단순한 스칼라 변수가 아닌 경우에는 중괄호를 추가로 적어줘야 하고,
어떤 씽이의 레퍼런스 카운트를 셀 때, 세어지지 않는 레퍼런스. 펄5.6이상에서 지원. 펄5.8에서는 코어 모듈인 Scalar::Util에서, 5.6에서는 WeakRef 모듈에서 weaken()을 제공한다. 그 씽이의 레퍼런스 카운트가 0이 되면 weak reference는 undef값을 갖는다.18
{
my$var;
$ref = \$var;
weaken($ref); # Make $ref a weak reference
}
# $ref is now undef
for($i=0; $i<100000; $i++) {
my$x = {};
my$y = {};
$x->{Y} = $y;
$y->{X} = $y;
# $x와 $y가 서로를 레퍼런스하기 때문에, 레퍼런스 카운트가 각각 2가 되어서 매 루프마다 생성된 두 익명 해쉬가# 계속 메모리에 남아 있게 된다.
weaken($x->{Y}); # 두 레퍼런스 중 하나를 weak reference로 만들면 해결
} # no memory leak
OOP에서는, 어떤 오브젝트를 참조하는 변수가 스코프를 벗어났음에도 불구하고 여전히 오브젝트 씽이가 메모리에 남는 경우를 해결하는 데 사용할 수 있다.20
# chapter 13.7의 예제## Animal 클래스 내에서use Scalar::Util qw(weaken); # in 5.8 and lateruse WeakRef qw(weaken); # in 5.6 after CPAN installationsubnamed{ # 생성자ref(my$class = shift) andcroak'class only';
my$name = shift;
my$self = { Name => $name, Color => $class->default_color };
bless$self, $class;
$REGISTRY{$self} = $self; # %REGISTRY는 생성된 Animal 오브젝트들을 보관하는 클래스 변수
weaken($REGISTRY{$self});
$self;
}
## 사용예my@horses = map Horse->named($_), ('Trigger', 'Mr. Ed');
print"alive before block:\n", map("$_\n", Animal->registered);
{
my@cows = map Cow->named($_), qw(Bessie Gwen);
my@racehorses = RaceHorse->named('Billy Boy');
print"alive inside block:\n", map("$_\n", Animal->registered);
}
# 이 블록을 벗어나는 순간 Bessie, Gwen, Billy Boy는 DESTROY된다.# %REGISTRY 해쉬의 값으로 남아 있었기 때문에 원래는 소멸하지 않았을 것이다.print"alive after block:\n", map("$_\n", Animal->registered);
print"End of program.\n";
package FullString;# FullString 이라는 패키지use Tie::Scalar; # Tie::Scalar 모듈을 읽어온다.our@ISA = qw(Tie::Scalar); # Tie::Scalar 클래스를 상속받음.# tie $str2, 'FullString', \$str1, "world"; 라고 하면 이 TIESCALAR가 불린다.subTIESCALAR{
my$class = shift; # 첫번째 인자는 'FullString'my$prefix_ref = shift; # 두번째 인자는 $str1의 레퍼런스my$body = shift; # 세번째 인자는 "world"my@realdata = ($prefix_ref, $body); # ( $str1의 레퍼런스, "world" ) 라는 배열이 내가 저장하고자 하는 자료구조returnbless\@realdata, $class; # 이 배열의 레퍼런스를 FullString 클래스의 오브젝트라고 지정하고 리턴
}
# $str2 의 값을 읽을 때는 이 FETCH가 불린다# 예를 들어 print $str2;subFETCH{
my$self = shift; # 첫번째 인자로 넘어온 게 $str2에 바인드된... 바로 저 배열의 레퍼런스!# 배열의 첫번째 원소 $self->[0]에 들어 있는 것은 $str1의 레퍼런스이므로, dereference하면 $str1의 값# 두번째 원소 $self->[1]에 들어 있는 것은 "world"return ${$self->[0]} . $self->[1]; # 결과적으로, "$str1에 들어있는 값"과 "world"를 합친 문자열 반환
}
# $str2에 어떤 값을 할당할 때 자동으로 불리는 STORE# STORE는 사실 이 경우 없어도 되는데, 이왕 tie 쓰는 법을 테스트하는 김에 :-)# $str2 = "LINUX!"; 와 같이 $str2의 값을 나중에라도 바꾸면# $str2는 $str1의 값 뒤에 "LINUX!"가 붙는 형태가 될 수 있도록 함subSTORE{
my$self = shift; # 역시 첫번째 인자는 배열의 레퍼런스my$new_body = shift; # 두번째 인자는 저 할당문의 등호 우측의 값$self->[1] = $new_body; # 배열의 두번째 원소의 값을 교체# 이 경우 전체 배열은 ( $str1의 레퍼런스, "LINUX!" )가 될 것임
}
이걸 사용하는 예
my ($str1, $str2);
$str1 = "hello ";
# $str2 = $str1 . "world"; 이것 대신에tie$str2, 'FullString', \$str1, "world"; # 이렇게 $str2와 FullString클래스를 바인드만 해주면# 이하에 있는 $str2를 액세스하는 코드는 전혀 고칠 필요 없다!!! :-)print"$str2\n"; # 이건 hello world$str1 = "bye! "; # $str1 을 바꾸면print"$str2\n"; # 자동으로 bye! world 가 됨$str2 = "LINUX!"; # 만일 $str2에 새로운 스트링을 할당하면?print"$str2\n"; # 이건 bye! LINUX!