[첫화면으로]Perl/OOP

마지막으로 [b]

Perl에서 OOP

1. 개괄
2. 오브젝트 생성, 사용
3. 생성자
4. Get, Set 메쏘드
4.1. setter의 반환값
5. 클래스 메쏘드와 인스턴스 메쏘드
6. "Person" 클래스 샘플
7. 상속
7.1. UNIVERSAL methods
7.2. 서브클래스의 생성자
8. AUTOLOAD
8.1. AUTOLOAD를 사용하여 getter/setter 제공
9. getter/setter 생성을 쉽게
10. Destruction
11. Indirect Object Notation
12. Weak Reference
13. tie
14. 기타 & comment

1. 개괄

2. 오브젝트 생성, 사용

my $ftp = Net::FTP->new("ftp.cpan.org")
    or die "Couldn't connect: $@\n";
# 이것은 아래와 같다
my $ftp = Net::FTP::new("Net::FTP", "ftp.cpan.org");
$ftp->login("anonymous");
# 이것은 아래와 같다
Net::FTP::login($ftp, "anonymous");

3. 생성자

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;
}

4. Get, Set 메쏘드

$obj->address();               # get
$obj->address("Seoul, Korea"); # set

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};
}

4.1. setter의 반환값

Perl의 서브루틴은 항상 어떤 값을 return하므로, setter도 뭔가 의미있는 값을 리턴하게 할 수 있다. 주로 다음 중에서 리턴함1
sub set_color {
  my $self = shift;
  $self->{Color} = shift;
}

# 이 경우 다음과 같이 여러 오브젝트에 반복 사용할 수 있다.
$tv_horse->set_color( $eating->set_color( color_from_user() ));
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);
}
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;
  }
}
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');
어떤 것을 리턴하든지간에, 일관성있게 하고, 문서화를 잘 하고, 일단 정해져서 배포가 되었으면 버전업이 되더라도 웬만하면 바꾸지 말자.

5. 클래스 메쏘드와 인스턴스 메쏘드

첫번째 인자를 ref으로 검사함으로써, 클래스 또는 인스턴스 어느 쪽에 대하여 호출되더라도 동작하게 할 수 있다.2
# 이 예문의 Horse 클래스의 인스턴스는 단지 스트링의 레퍼런스임

sub name {
  my $either = shift;
  ref $either
    ? $$either                # it's an instance, return name
    : "an unnamed $either";   # it's a class, return generic
}

클래스 전용 또는 인스턴스 전용 메쏘드의 예:3
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 /디버깅)

6. "Person" 클래스 샘플

Person.pm 으로 저장하고, 사용하는 쪽에서는 use Person;

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 <<EOF;
$name
$address

$date

Dear $forename,

$body

Yours faithfully,
EOF
   return $self;
}

1;

위의 printletter() 서브루틴처럼, 딱히 리턴할 게 없는 메쏘드에서는 오브젝트 레퍼런스 자신을 다시 리턴해주면 좋다. $obj->sub1()->sub2()->sub3()과 같은 식의 호출이 가능하기 때문이다4

7. 상속

(주석이 따로 붙어있지 않은 단락은 "Beginning Perl"의 내용)

패키지 글로벌 변수 @ISA에 슈퍼클래스 리스트를 적어준다.
package Employee;

use Person;
our @ISA = qw(Person);

어떤 클래스->메쏘드를 불렀는데 그 클래스에 그 메쏘드가 없으면 @ISA에 나열된 클래스에서 메쏘드를 찾는다. 만일 슈퍼클래스에도 역시 @ISA가 있다면 재귀적으로 검사를 하게 된다. ISA 검사는 깊이 우선 탐색으로 이뤄짐.5

슈퍼클래스가 다른 파일에 있는 경우는 use아 @ISA를 따로 쓰지 않고 base 프라그마를 쓸 수도 있다.6
package Cow;
use Animal;
use vars qw(@ISA);  # 펄5.6 미만 버전을 고려해서 our를 쓰지 않고 패키지 변수 @ISA를 선언
@ISA = qw(Animal);

# 위 대신 아래와 같이 가능
package Cow;
use base qw(Animal);

SUPER::method 는 슈퍼클래스의 method()를 실행한다
   $self->SUPER::_init();

샘플
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();
}

7.1. UNIVERSAL methods

UNIVERSAL : 최상위 클래스. 다음 메쏘드들을 제공한다
# Horse 또는 그 서브클래스인 오브젝트만 추출
my @horses = grep $_->isa('Horse'), @all_animals;
# 참고, 아래는 오직 Horse 클래스인 경우만 추출
my @horses_only = grep ref $_ eq 'Horse', @all_animals;
if (eval { $unknown_thing->isa('Animal') }) {
   # it's an Animal...
}
if (eval { $tv_horse->can('eat') }) {
  $tv_horse->eat('hay');
}

UNIVERSAL 클래스에 메쏘드를 추가하면 모든 클래스의 오브젝트에 대해 호출할 수 있다.9
sub UNIVERSAL::fandango {
  warn 'object ', shift, " can do the fandango!\n";
}

7.2. 서브클래스의 생성자

8. AUTOLOAD

어떤 메쏘드를 수행하려는데 상속 트리 내에 그 메쏘드가 없을 경우, Perl은 동일한 트리에서 AUTOLOAD라는 이름의 메쏘드를 찾아보고, 있으면 그 메쏘드를 실행한다.10

복잡하지만 잘 사용되지 않는 메쏘드를, 미리 컴파일하지 않고 사용할 때 컴파일 하는 용도로 쓸 수 있다.11
## 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:AutoLoaderPerlDoc:SelfLoader 모듈을 참고.

8.1. AUTOLOAD를 사용하여 getter/setter 제공

AUTOLOAD를 여러 가지 멤버 변수의 getter/setter로 사용하는 예12
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";
}

9. getter/setter 생성을 쉽게

Cpan:Class::MethodMakerCpan:Class::Accessor 사용13

10. Destruction

오브젝트의 레퍼런스 카운트가 0이 되어서 메모리에서 사라질 때, 해당 오브젝트에 대해 DESTROY 메쏘드가 자동으로 호출된다.14

A클래스의 오브젝트가 B클래스의 오브젝트 레퍼런스를 포함하고 있을때, A가 소멸하여 B 오브젝트의 레퍼런스 카운트가 0이 되어야 B도 소멸된다. B를 먼저 없애고 싶다면 A의 DESTROY 메쏘드에서 처리해 줄 필요가 있음.15

소멸자에는 항상 $self->SUPER::DESTROY 를 넣어서 슈퍼클래스의 소멸자를 불러주는 것이 좋다.16

11. Indirect Object Notation

Indirect Object Notation17
# 아래와 같은 형태를
Class->class_method(@args);
$instance->instance_method(@other);
# 아래와 같이 쓸 수도 있다
class_method Class @args;
instance_method $instance @other;

# 대표적인 예 - 오브젝트 생성
my $obj = new Some::Class @constructor_params;
Perl5 초기에 많이 사용된 형태이나, 쓰지 않는 것을 권장함:
instance_method { $somehash->{$somekey}->[42] } @params;
# 아래의 코드는
print name $cow, " eats.\n";
# 이걸로 해석된다. (잘못)
print name ($cow, " eats.\n");
# 원래의 의도는
print $cow->name, " eats.\n";

12. Weak Reference

어떤 씽이의 레퍼런스 카운트를 셀 때, 세어지지 않는 레퍼런스. 펄5.6이상에서 지원. 펄5.8에서는 코어 모듈인 PerlDoc:Scalar::Util에서, 5.6에서는 Cpan:WeakRef 모듈에서 weaken()을 제공한다. 그 씽이의 레퍼런스 카운트가 0이 되면 weak reference는 undef값을 갖는다.18
    {
      my $var;
      $ref = \$var;
      weaken($ref);                     # Make $ref a weak reference
    }
    # $ref is now undef

이것은 데이타들이 원형으로 참조하고 있을 때 메모리 누수를 막는데 유용하다.19
        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 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";

13. tie

이런 게 가능하다니... =ㅅ=;

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!" )가 될 것임
}

이걸 사용하는 예

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!

14. 기타 & comment

[Perl 객체? Python과 비교하며 감을 잡아보자. | KLDP]
-- Raymundo 2007-5-2 12:24 pm
이름:  
Homepage:
내용:
 


컴퓨터분류
각주:
1. 2. 3. Intermediate Perl, Chapter 12
4. -_-;;; 주인장은 그게 과연 좋은 건지 의심스럽다
5. 6. Intermediate Perl, Chapter 11
7. 9. 10. 11. 12. 13. Intermediate Perl, Chapter 14
8. Beginning Perl
14. 15. 16. 17. 20. Intermediate Perl, Chapter 13
18. perldoc Scalar::Util
19. perldoc WeakRef

마지막 편집일: 2015-1-5 1:46 pm (변경사항 [d])
5621 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기