37 번째 수정본 소스 보기 : Perl/OOP
마지막으로 [b]
-- Loading page list... --
내용출력
로그인[l]
Diary
[f]
최근변경내역
[r]
페이지목록[i]
횡설수설[2]
게시판[3]
링크
수정할 수 없습니다: Perl/OOP 는 읽기 전용 페이지입니다.
[[Perl]]에서 OOP
== # 개괄 == * 클래스는 패키지로 구현 * 메쏘드 - 패키지 내의 서브루틴 * 펄은 어떤 서브루틴이 클래스 메쏘드(자바로 치면 static?), 오브젝트 메쏘드, 평범한 서브루틴 중 어느 것인지 알 수 없기 때문에 프로그래머가 체크해야 함 * 클래스 변수는 패키지 변수 * 오브젝트는 일종의 해쉬 레퍼런스로 생각할 수 있다 ** 오브젝트 변수는 해쉬 엔트리 * constructor 는 new() * destructor - DESTROY * private 메쏘드는 이름을 "_"로 시작하게 짓는다. 그러나 Perl에게는 아무런 의미가 없고, 코드를 보는 사람에게만 의미가 있는 관례적인 것일 뿐. == # 오브젝트 생성, 사용 == * 오브젝트 생성 : 클래스이름->new() ** "->method()"는 일반적인 서브루틴 호출과 비교해서 다음이 다르다 *** 서브루틴이 존재하지 않는 경우 슈퍼클래스에서 찾아본다. *** "->"앞에 있는 게 서브루틴의 첫번째 인자로 넘어간다. {{{#!vim perl my $ftp = Net::FTP->new("ftp.cpan.org") or die "Couldn't connect: $@\n"; # 이것은 아래와 같다 my $ftp = Net::FTP::new("Net::FTP", "ftp.cpan.org"); }}} * 생성된 오브젝트 사용 {{{#!vim perl $ftp->login("anonymous"); # 이것은 아래와 같다 Net::FTP::login($ftp, "anonymous"); }}} * $ftp->login()이 Net::FTP 패키지에 있는 login을 부른다는 것을 어떻게 아는가? 생성자가 오브젝트 레퍼런스를 생성하고 "bless"라는 연산을 수행함으로써 그 레퍼런스가 어느 클래스에 속해 있는 것인지 정보를 제공한다. == # 생성자 == {{{#!vim perl package Person; # 클래스 이름을 패키지 이름으로 지정 # Class for storing data about a person use warnings; use strict; sub new { my $class = shift; # 클래스 이름이 자동으로 첫번째 인자로 넘어옴 my $self = {@_}; # new()의 인자로 해쉬 형태 리스트가 들어온 경우 bless($self, $class); # 해쉬 레퍼런스가 $class의 인스턴스임을 지정 # $class 대신 명시적으로 "Person"이라고 하는 것은 좋지 않다. # Person의 서브클래스에 new()가 없을 경우 이곳의 new()가 불리기 때문 return $self; } }}} == # Get, Set 메쏘드 == {{{#!vim perl $obj->address(); # get $obj->address("Seoul, Korea"); # set }}} {{{#!vim perl sub address { my $self = shift; unless (ref $self) { # 오브젝트 메쏘드를 클래스 메쏘드로 호출한 경우 에러 처리 confess "Should call surname() with an object, not a class"; } my $data = shift; $self->{address} = $data if defined $data; return $self->{address}; } }}} === # setter의 반환값 === Perl의 서브루틴은 항상 어떤 값을 return하므로, setter도 뭔가 의미있는 값을 리턴하게 할 수 있다. 주로 다음 중에서 리턴함
* 업데이트된 파라메터 (즉 호출할 때 넘겨준 인자 그대로) {{{#!vim perl sub set_color { my $self = shift; $self->{Color} = shift; } # 이 경우 다음과 같이 여러 오브젝트에 반복 사용할 수 있다. $tv_horse->set_color( $eating->set_color( color_from_user() )); }}} * 업데이트되기 전의 기존 파라메터 값 {{{#!vim perl sub set_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를 저장하여 반환하는 수고를 할 필요가 없으므로, {{{#!vim perl sub set_color { my $self = shift; if (defined wantarray) { # this method call is not in void context, so # the return value matters my $old = $self->{Color}; $self->{Color} = shift; $old; } else { # this method call is in void context $self->{Color} = shift; } } }}} * 오브젝트 자신 {{{#!vim perl sub set_color { my $self = shift; $self->{Color} = shift; $self; } # 이 경우 chain setting 가능 my $tv_horse = Horse->named('Mr. Ed') ->set_color('grey') ->set_age(4) ->set_height('17 hands'); }}} * 성공/실패 코드 어떤 것을 리턴하든지간에, 일관성있게 하고, 문서화를 잘 하고, 일단 정해져서 배포가 되었으면 버전업이 되더라도 웬만하면 바꾸지 말자. == # 클래스 메쏘드와 인스턴스 메쏘드 == 첫번째 인자를 ref으로 검사함으로써, 클래스 또는 인스턴스 어느 쪽에 대하여 호출되더라도 동작하게 할 수 있다.
{{{#!vim perl # 이 예문의 Horse 클래스의 인스턴스는 단지 스트링의 레퍼런스임 sub name { my $either = shift; ref $either ? $$either # it's an instance, return name : "an unnamed $either"; # it's a class, return generic } }}} 클래스 전용 또는 인스턴스 전용 메쏘드의 예:
{{{#!vim perl use Carp qw(croak); sub instance_only { ref(my $self = shift) or croak "instance variable needed"; ... use $self as the instance ... } sub class_only { ref(my $class = shift) and croak "class name needed"; ... use $class as the class ... } }}} PerlDoc:Carp 모듈이 제공하는 croak()은 die()와 유사하고, carp()는 warn()과 유사하나, 에러가 발생한 지점이 아니라 그 메쏘드를 호출한 지점을 알려준다. 모듈을 만들때는 die나 warn대신에 Carp모듈을 사용할 것. (see [[/디버깅]]) == # "Person" 클래스 샘플 == Person.pm 으로 저장하고, 사용하는 쪽에서는 use Person; {{{#!vim perl package Person; # Class for storing data about a person use warnings; use strict; use Carp; my @Everyone; # 클래스 변수 # Constructor and initialisation sub new { my $class = shift; my $self = {@_}; bless($self, $class); $self->_init; return $self; } sub _init { my $self = shift; push @Everyone, $self; carp "New object created"; } # Object accessor methods sub address { $_[0]->{address }=$_[1] if defined $_[1]; $_[0]->{address } } sub surname { $_[0]->{surname }=$_[1] if defined $_[1]; $_[0]->{surname } } sub forename { $_[0]->{forename}=$_[1] if defined $_[1]; $_[0]->{forename} } sub phone_no { $_[0]->{phone_no}=$_[1] if defined $_[1]; $_[0]->{phone_no} } sub occupation { $_[0]->{occupation}=$_[1] if defined $_[1]; $_[0]->{occupation} } # Class accessor methods sub headcount { scalar @Everyone } sub everyone { @Everyone } # Utility methods sub fullname { my $self = shift; return $self->forename." ".$self->surname; } sub printletter { my $self = shift; my $name = $self->fullname; my $address = $self->address; my $forename= $self->forename; my $body = shift; my @date = (localtime)[3,4,5]; $date[1]++; # Months start at 0! Add one to humanise! $date[2]+=1900; # Add 1900 to get current year. my $date = join "/", @date; print <
sub1()->sub2()->sub3()과 같은 식의 호출이 가능하기 때문이다
== # 상속 == (주석이 따로 붙어있지 않은 단락은 "Beginning Perl"의 내용) 패키지 글로벌 변수 @ISA에 슈퍼클래스 리스트를 적어준다. {{{#!vim perl package Employee; use Person; our @ISA = qw(Person); }}} 어떤 클래스->메쏘드를 불렀는데 그 클래스에 그 메쏘드가 없으면 @ISA에 나열된 클래스에서 메쏘드를 찾는다. 만일 슈퍼클래스에도 역시 @ISA가 있다면 재귀적으로 검사를 하게 된다. ISA 검사는 '''깊이 우선''' 탐색으로 이뤄짐.
슈퍼클래스가 다른 파일에 있는 경우는 use아 @ISA를 따로 쓰지 않고 base 프라그마를 쓸 수도 있다.
{{{#!vim perl package Cow; use Animal; use vars qw(@ISA); # 펄5.6 미만 버전을 고려해서 our를 쓰지 않고 패키지 변수 @ISA를 선언 @ISA = qw(Animal); # 위 대신 아래와 같이 가능 package Cow; use base qw(Animal); }}} SUPER::method 는 슈퍼클래스의 method()를 실행한다 {{{#!vim perl $self->SUPER::_init(); }}} 샘플 {{{#!vim perl package Employee; use Person; use warnings; use strict; our @ISA = qw(Person); sub employer { $_[0]->{employer}=$_[1] if defined $_[1]; $_[0]->{employer} } sub position { $_[0]->{position}=$_[1] if defined $_[1]; $_[0]->{position} } sub salary { $_[0]->{salary }=$_[1] if defined $_[1]; $_[0]->{salary } } sub raise { my $self = shift; my $newsalary = $self->salary + 2000; $self->salary($newsalary); return $self; } # 메쏘드 override sub _init { my $self = shift; my $employer = $self->employer || "unknown"; unless (ref $employer) { my $new_o = Person->new( surname => $employer ); $self->employer($new_o); } $self->SUPER::_init(); } }}} === # UNIVERSAL methods === UNIVERSAL : 최상위 클래스. 다음 메쏘드들을 제공한다 * isa($package) : $package 또는 그 서브클래스(의 인스턴스)이면 true
{{{#!vim perl # Horse 또는 그 서브클래스인 오브젝트만 추출 my @horses = grep $_->isa('Horse'), @all_animals; # 참고, 아래는 오직 Horse 클래스인 경우만 추출 my @horses_only = grep ref $_ eq 'Horse', @all_animals; }}} ** $obj->isa('Class')는, $obj가 bless된 레퍼런스이거나, 패키지 이름을 담고 있는 스칼라 변수가 아니면 에러가 난다. 따라서 eval 로 묶는 것이 좋음 {{{#!vim perl if (eval { $unknown_thing->isa('Animal') }) { # it's an Animal... } }}} * can($method) : $method라는 이름의 메쏘드를 실행할 수 있으면 true ** 역시 eval로 묶는 것이 안전 {{{#!vim perl if (eval { $tv_horse->can('eat') }) { $tv_horse->eat('hay'); } }}} * VERSION : $VERSION 변수가 있으면 그 값을 반환
UNIVERSAL 클래스에 메쏘드를 추가하면 모든 클래스의 오브젝트에 대해 호출할 수 있다.
{{{#!vim perl sub UNIVERSAL::fandango { warn 'object ', shift, " can do the fandango!\n"; } }}} === # 서브클래스의 생성자 === * http://stackoverflow.com/questions/5249902/calling-base-constructor-in-perl - Calling base constructor in perl ** 딱히 서브클래스에서 할 일이 없다면 그냥 상위클래스의 생성자를 부르는 것으로 충분하다. ** 여러가지 세팅을 해야 한다면, 애초에 new는 단지 객체에 bless를 하여 반환하는 용도로 쓰고, 나머지 세팅은 별도의 메쏘드를 부르는 게 낫다. 그러면 다중 상속을 할 경우 어느 상위클래스의 생성자가 사용되든, 나머지 세팅은 각 상위클래스의 메쏘드를 각각 불러서 할 수 있다. (그런데 이것은 상위 클래스까지 내가 직접 만들었을 때 얘기고...) == # AUTOLOAD == 어떤 메쏘드를 수행하려는데 상속 트리 내에 그 메쏘드가 없을 경우, Perl은 동일한 트리에서 AUTOLOAD라는 이름의 메쏘드를 찾아보고, 있으면 그 메쏘드를 실행한다.
* 인자는 원래의 메쏘드에 넘어가는 인자를 그대로 사용. * 원래 메쏘드의 이름은 fully qualified 형태로, AUTOLOAD메쏘드가 있는 패키지 내의 패키지 변수 $AUTOLOAD에 담김 복잡하지만 잘 사용되지 않는 메쏘드를, 미리 컴파일하지 않고 사용할 때 컴파일 하는 용도로 쓸 수 있다.
{{{#!vim perl ## in Animal sub AUTOLOAD { our $AUTOLOAD; (my $method = $AUTOLOAD) =~ s/.*:://s; # remove package name if ($method eq "eat") { # 원래 호출한 게 eat 메쏘드였다면 ## define eat: eat메쏘드를 이 지점에서 정의 eval q{ sub eat { ... long definition goes here ... } }; # End of eval's q{ } string die $@ if $@; # if typo snuck in goto &eat; # jump into it } else { # unknown method croak "$_[0] does not know how to $method\n"; } } }}} 위의 예에서, 일단 AUTOLOAD가 수행되었으면 eat이 정의되었기 때문에 이후에는 바로 eat이 호출된다. 오토로딩을 끄거나 하는 작업을 쉽게 하려면 PerlDoc:AutoLoader""나 PerlDoc:SelfLoader 모듈을 참고. === # AUTOLOAD를 사용하여 getter/setter 제공 === AUTOLOAD를 여러 가지 멤버 변수의 getter/setter로 사용하는 예
{{{#!vim perl sub AUTOLOAD { my @elements = qw(color age weight height); # 네 가지 getter our $AUTOLOAD; if ($AUTOLOAD =~ /::(\w+)$/ and grep $1 eq $_, @elements) { my $field = ucfirst $1; # 필드명은 Color, Age 등 첫글자가 대문자라서 { no strict 'refs'; *{$AUTOLOAD} = sub { $_[0]->{$field} }; # getter메쏘드를 $field를 접근하는 closure로 구현 # 만일 setter라면 *{$AUTOLOAD} = sub { $_[0]->{$field} = $_[1] }; } goto &{$AUTOLOAD}; } croak "$_[0] does not understand $method\n"; } }}} == # getter/setter 생성을 쉽게 == Cpan:Class::MethodMaker""나 Cpan:Class::Accessor 사용
정작 CPAN에 있는 문서에는 new_with_init 등에 대한 설명이 없네...) {{{#!vim perl package Animal; use Class::MethodMaker new_with_init => 'new', get_set => [-eiffel => [qw(color height name age)]], abstract => [qw(sound)], ; sub init { my $self = shift; $self->set_color($self->default_color); } sub named { my $self = shift->new; $self->set_name(shift); $self; } sub speak { my $self = shift; print $self->name, ' goes ', $self->sound, "\n"; } sub eat { my $self = shift; my $food = shift; print $self->name, " eats $food\n"; } sub default_color { 'brown'; } }}}
== # Destruction == 오브젝트의 레퍼런스 카운트가 0이 되어서 메모리에서 사라질 때, 해당 오브젝트에 대해 DESTROY 메쏘드가 자동으로 호출된다.
A클래스의 오브젝트가 B클래스의 오브젝트 레퍼런스를 포함하고 있을때, A가 소멸하여 B 오브젝트의 레퍼런스 카운트가 0이 되어야 B도 소멸된다. B를 먼저 없애고 싶다면 A의 DESTROY 메쏘드에서 처리해 줄 필요가 있음.
소멸자에는 항상 $self->SUPER::DESTROY 를 넣어서 슈퍼클래스의 소멸자를 불러주는 것이 좋다.
== # Indirect Object Notation == Indirect Object Notation
{{{#!vim perl # 아래와 같은 형태를 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이거나 단순한 스칼라 변수가 아닌 경우에는 중괄호를 추가로 적어줘야 하고, {{{#!vim perl instance_method { $somehash->{$somekey}->[42] } @params; }}} * parsing시에 모호한 경우가 생긴다 {{{#!vim perl # 아래의 코드는 print name $cow, " eats.\n"; # 이걸로 해석된다. (잘못) print name ($cow, " eats.\n"); # 원래의 의도는 print $cow->name, " eats.\n"; }}} == # Weak Reference == 어떤 씽이의 레퍼런스 카운트를 셀 때, 세어지지 않는 레퍼런스. 펄5.6이상에서 지원. 펄5.8에서는 코어 모듈인 PerlDoc:Scalar::Util""에서, 5.6에서는 Cpan:WeakRef 모듈에서 weaken()을 제공한다. 그 씽이의 레퍼런스 카운트가 0이 되면 weak reference는 undef값을 갖는다.
{{{#!vim perl { my $var; $ref = \$var; weaken($ref); # Make $ref a weak reference } # $ref is now undef }}} 이것은 데이타들이 원형으로 참조하고 있을 때 메모리 누수를 막는데 유용하다.
{{{#!vim perl 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에서는, 어떤 오브젝트를 참조하는 변수가 스코프를 벗어났음에도 불구하고 여전히 오브젝트 씽이가 메모리에 남는 경우를 해결하는 데 사용할 수 있다.
{{{#!vim perl # chapter 13.7의 예제 ## Animal 클래스 내에서 use Scalar::Util qw(weaken); # in 5.8 and later use WeakRef qw(weaken); # in 5.6 after CPAN installation sub named { # 생성자 ref(my $class = shift) and croak '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"; }}} == # tie == 이런 게 가능하다니... =ㅅ=; {{{#!vim perl package FullString; # FullString 이라는 패키지 use Tie::Scalar; # Tie::Scalar 모듈을 읽어온다. our @ISA = qw(Tie::Scalar); # Tie::Scalar 클래스를 상속받음. # tie $str2, 'FullString', \$str1, "world"; 라고 하면 이 TIESCALAR가 불린다. sub TIESCALAR { my $class = shift; # 첫번째 인자는 'FullString' my $prefix_ref = shift; # 두번째 인자는 $str1의 레퍼런스 my $body = shift; # 세번째 인자는 "world" my @realdata = ($prefix_ref, $body); # ( $str1의 레퍼런스, "world" ) 라는 배열이 내가 저장하고자 하는 자료구조 return bless \@realdata, $class; # 이 배열의 레퍼런스를 FullString 클래스의 오브젝트라고 지정하고 리턴 } # $str2 의 값을 읽을 때는 이 FETCH가 불린다 # 예를 들어 print $str2; sub FETCH { 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!"가 붙는 형태가 될 수 있도록 함 sub STORE { my $self = shift; # 역시 첫번째 인자는 배열의 레퍼런스 my $new_body = shift; # 두번째 인자는 저 할당문의 등호 우측의 값 $self->[1] = $new_body; # 배열의 두번째 원소의 값을 교체 # 이 경우 전체 배열은 ( $str1의 레퍼런스, "LINUX!" )가 될 것임 } }}} 이걸 사용하는 예 {{{#!vim perl 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! }}} == # 기타 & comment == 요새 펄 OOP의 대세는 [[/Moose]]나 [[/Moo]]를 쓰는 것인 듯.
[http://kldp.org/node/77924 Perl 객체? Python과 비교하며 감을 잡아보자. | KLDP]
----
---- [[컴퓨터분류]]
Perl/OOP
페이지로 돌아가기 |
다른 수정본 보기