perltoot - Tom 의 Perl 오브젝트지향 튜토리얼
voltar para o topo
오브젝트지향 프로그램은, 요즘 인기입니다.
매니저에게는, 얇게 잘린 빵(좋은 발명을 일컫는 말) 보다도 오히려
오브젝트를 가지려는 사람도 있습니다.
어떻습니까?
오브젝트의, 뭐가 그렇게 특별한 걸까요?
그전에 도대체 오브젝트라는 것은
뭘까요?
오브젝트는 제대로 된 작고 사용하기 쉬운 포장지에 포장해서,
복잡한 행동을 멀리 날려버리는 방법 이외의 무엇도 아닙니다.
(전문가는 이것을 추상화라고 부릅니다).
정말로 어려운 문제를 정리려면, 몇 주씩이나 팟 하고 지나가버리고,
똑똑한 사람들은, 보통의 사람이라도 사용할 수 있는 그런 훌륭한 오브젝트를
만듭니다(전문가는, 이것을 소프트웨어의 재이용이라고 부릅니다).
오브젝트를 사용하는 사람(아마도 프로그래머)는, 자신이 원하는 작은 포장을
건드릴 수 있습니다.
하지만, 그 포장을 열려고는 하지 않고, 그 안을 어지럽히려고도 하지않겠죠.
딱, 하드웨어의 비싼 부분처럼, 계약은 다음처럼 되어있습니다.
``커버를 벗기면, 보증은 무효하게 됩니다. ''라고...
그래서, 그런 것은 해서는 안됩니다.
오브젝트의 핵심은, 클래스입니다.
클래스는, 작은 이름공간이고, 데이터와 함수로 구성됩니다.
클래스는 관련된 루틴의 세트이고, 몇개인가의 문제영역을 취급합니다.
클래스를 유저정의의 형태로 생각할 수도 있습니다.
Perl 의 패키지의 구성은, 보다 전통적인 모듈처럼, 클래스모듈에도 사용됩니다.
오브젝트는, 클래스 안에, ``살고'' 있습니다.
이 것은, 오브젝트가 몇개인가의 패키지에 소속되어 있는 것을 의미합니다.
클래스는, 클래스를 사용하는 사람에게 몇개인가의 작은 묶음을 제공합니다.
그것들의 묶음이 오브젝트입니다.
오브젝트는, 자신이 소속하고 있는 클래스를 알고 있고, 어떤 행동을 하는지도
알고 있습니다.
클래스를 사용하는 사람은 클래스에 뭔가를 하도록, 예를 들어,
``오브젝트를 줘'' 라고 하는 것처럼, 받기를 원하는 지,
오브젝트중 하나에 뭔가를 하기를 바랄 수도 있습니다.
클래스에 뭔가를 하도록 원하는 것은,
클래스 메소드를 부르는 것입니다.
오브젝트에 뭔가를 하도록 원하는 것은,
오브젝트메소드를 부르는 것입니다.
클래스(보통) 또는, 오브젝트(때때로)에, 오브젝트를 반환하기를
원하는 것은,
생성자를 호출하는 것입니다.
생성자는 메소드의 한 종류입니다.
그것은 그렇다치고, 오브젝트는 Perl 이외의 데이터 형태와 어떻게
다른 것일까요?
실제로는, 오브젝트는.. 오브젝트의 기초의 형태는 뭘까요?
첫번째 질문에 답하는 것은 간단합니다.
오브젝트는 하나의, 단 하나의 방법에 의해, Perl 의 다른 데이터 형태와는
다릅니다.
오브젝트를 디리퍼런스하는 것에, 단순한 배열이나 해쉬처럼
문자열이나 숫자의 첨자가 아닌, 이름이 붙은 서브루틴의 호출을 사용합니다.
한마디로 하면,
메소드 로 디리퍼런스합니다.
두번째의 답은, 다음과 같습니다.
오브젝트는 리퍼런스이지만, 단순한 리퍼런스가 아닙니다.
주의해주세요.
특별한 클래스(패키지)에
축복(bless)받은 리퍼런스를 가지고 있는 것입니다.
리퍼런스의 종류는?
아마도, 그 질문의 답은, 그다지 구체적이지 않습니다.
Perl 에서는, 기초가 되는 고유의 데이터형태로써, 리퍼런스라면 어떤 종류의
것이라도, 클래스 설계자가 좋을대로 사용할 수 있기 때문입니다.
리퍼런스라면, 스칼라에서도, 배열에서도, 해쉬에서라도 상관없습니다.
코드의 리퍼런스도 가능합니다.
하지만, 가지기 전의 유연성에 의해, 보통 오브젝트는 해쉬리퍼런스입니다.
voltar para o topo
(클래스 만들기)
클래스를 만들기 전에, 클래스에 어떤 이름을 붙일까를 결정해야 합니다.
클래스(패키지)의 이름은, 보통 모듈처럼 클래스를 넣는 파일이름을 좌우합니다.
그리고, 그 클래스(패키지)는 하나 이상의 방법으로 오브젝트를 생성하는 방법을
제공해야합니다.
마지막에 클래스(패키지)는, 오브젝트를 사용하는 사람에게 떨어진 곳에서
간접적으로 오브젝트를 조작하는 것이 가능한 메커니즘을 제공해야 합니다.
예를 들면, 단순한 Person 클래스모듈을 만들어봅시다.
이 클래스 모듈은 Person.pm 으로 저장됩니다.
만약, 이 클래스모듈이, Happy::Person 클래스로 불려지면,
이 클래스는, Happy/Person.pm 파일에 보존됩니다.
그리고, 이 패키지는 단순한 Person 이 아니라, Happy::Person 이 됩니다
(Unix 나, Plan 9 가 아닌 Mac OS 나 VMS 같은 OS 에서 움직이는
퍼스널컴퓨터위에서는 디렉토리의 구분자는 다르겠지만, 원리는 같습니다).
디렉토리 이름의 기본이 되는 모듈과의 사이에 어떤 공식적인 관계도
상정해서는 안됩니다.
이것은 단순히, 그룹을 나누는 것을 편리하게 하는 것는 것 뿐으로,
계승이나 변수의 접근성 등, 그외 여러가지에 어떤 효과도 주지 않습니다.
이 모듈에 Exporter 는 사용하지 맙시다.
우리들은 잘 움직이는 좋은 클래스 모듈은 아무것도 export 하지
않기때문입니다.
오브젝트를 만들기 위해서는, 클래에
생성자 메소드 가 필요합니다.
생성자는, 보통 데이터 형태가 아니라, 새로운 클래스의 오브젝트를 제공합니다.
이 마법은,
bless() 함수에 의해 취급됩니다.
bless() 의 유일한 목적은, 리퍼런스를 오브젝트로써
사용할 수 있도록 하는 것입니다.
기억하세요! : 오브젝트인 것은 실제로는 메소드가 그 오브젝트를 대신해서
불려진 것 이외에 아무 의미도 없습니다.
생성자는, 좋을대로 이름붙여도 상관없지만, 대부분의 Perl 프로그래머는,
생성자를
new() 라고 부르는 것을 좋아하는 듯 합니다.
하지만,
new() 는, 예약어가 아니고, 클래스에는 그런 것을 공급할 의무도 없습니다.
생성자로써, 클래스와 같은 이름의 함수를 사용하는 프로그래머가 있다는 것도
알수 있습니다.
(오브젝트의 표현)
Pascal 의 레코드나, C 의 구조체나, C++ 의 클래스를 나타내기 위해서,
Perl 로 사용되는, 제일 일반적인 메커니즘은 이름없는 해쉬입니다.
해쉬에는 임의의 수의 데이터영역이 있고, 자신이 붙인 임의의 이름으로
그것들에 접근하기 쉽기 때문입니다.
단순한 구조체같은 에뮬레이션을 하려면, 다음처럼 할 수가 있습니다.
$rec = {
name => "Jason",
age => 23,
peers => [ "Norbert", "Rhys", "Phineas"],
};
차이를 알고 싶다고 느낀다면, 대문자의 해쉬 키에 의해서,
보는 것만으로, 조금 다른 차이를 줄 수 가 있습니다.
$rec = {
NAME => "Jason",
AGE => 23,
PEERS => [ "Norbert", "Rhys", "Phineas"],
};
이것으로,
$rec->{NAME} 으로, ``Jason'' 을 발견할 수가 있게 되고,
또,
@{ $rec->{PEERS} } 로, ``Norbert'' 와, ``Rhys'' 와 ``Phineas'' 를
얻을 수가 있습니다.
(요즘, 얼마나 많은 23세의 프로그래머가 Jason이라는 이름을 가지고 있는 가
신경 쓴 적이 있나요? :-))
이것과 같은 모델은, 복수의 클래스로 자주 사용됩니다.
그렇다고 해도, 클래스의 밖에서, 모두가 오브젝트에 왈츠를 추도록 하고,
그 데이터 멤버에 어쩌구 저쩌구 해서 직접 접근하는 것은,
프로그램의 예의바른 정점을 깊이 생각한 것이 아닙니다.
간단하게, 오브젝트는
오브젝트 메소드를 사용해서 접근하고,
알지 못하는 쿠키라고 생각해야 합니다.
보기에는 메소드는 브라켓이랑 브레이스 대신에,
함수명을 사용하고, 리퍼런스를 디리퍼런스하고 있는 것처럼 보입니다.
(클래스의 인터페이스)
언어에는, 클래스의 메소드를 위해 정식 종합 인터페이스를 제공하고 있는 것도
있습니다만, Perl 은 그렇지 않습니다.
사용하는 사람이 그 나름대로의 클래의 문서를 읽는 것을 맞게 하고 있습니다.
정의되어 있지 않은 메소드를 오브젝트로 호출하면, Perl 은, 경고를 내려고
하지는 않지만, 프로그램은 실행하고 있는 중간에, 예외를 발생시킬 겁니다.
이처럼, 인수에 요소를 기대하는 메소드에게, 요소가 아닌 숫자를 인수로 해서
호출했다고 해도, 컴파일러가 그것을 알아차리는 것은 기대할 수 없습니다.
(아마도, 당신은 컴파일러에 당신의 좋아하는 모든 것을 기대했을 테지만,
그런 것은 일어나지 않습니다.)
Person 클래스를 사용하는 사람이 잘 교육받은 소정의 인터페이스를
설명하는 문서를 읽고 있는 사람이라고 상정해봅시다.
Person 클래스의 사용법이 여기에 있습니다.
use Person;
$him = Person->new();
$him->name("Jason");
$him->age(23);
$him->peers( "Norbert", "Rhys", "Phineas" );
push @All_Recs, $him; # 나중을 위해 오브젝트를 배열에 넣습니다.
printf "%s is %d years old.\n", $him->name, $him->age;
print "His peers are: ", join(", ", $him->peers), "\n";
printf "Last rec's name is %s\n", $All_Recs[-1]->name;
위처럼, 그 클래스를 사용하는 사람은, 오브젝트에 있는 특정의 구성이 있는 지,
다른 구성이 있는 지를 알지 못합니다(적어도, 그 사실에 주의를 기울여야 하는가는
관계 없습니다).
클래스와 그 오브젝트의 인터페이스는 안쪽 메소드를 경유합니다.
그리고 클래스를 사용하는 사람은 모두, 메소드를 건드려야 합니다.
(생성자와 인스턴스 메소드)
아직
어떤 것은, 오브젝트에 뭐가 있는 지를 알 필요가 있습니다.
그리고 그 어떤 것은 클래스입니다.
클래스는 프로그래머가 오브젝트에 접근하는 것에 사용하는 메소드를 구성합니다.
여기에 나타내는 것은 표준 오브젝트처럼 해쉬 리퍼런스를 사용하는 문법을
사용해, Person 클래스에 구성하는 방법입니다.
생성자로써 움직이는
new() 같은 클래스 메소드를 만듭시다.
그리고 3개의 오브젝트 메소드,
name() 과
age() 와
peers() 를 만들고
오브젝트마다, 이름없는 해쉬에 데이터를 숨기도록 합니다.
package Person;
use strict;
#########################################################
## 오브젝트의 생성자(단순화한 버젼) ##
#########################################################
sub new {
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
bless($self); # 아래를 봐주세요.
return $self;
}
#####################################################
## 오브젝트마다 데이터에 접근하는 메소드 ##
## ##
## 인수가 있다면, 값을 설정합니다. 인수가 있다면 ##
## 값을 반환합니다. ##
#####################################################
sub name {
my $self = shift;
if (@_) { $self->{NAME} = shift }
return $self->{NAME};
}
sub age {
my $self = shift;
if (@_) { $self->{AGE} = shift }
return $self->{AGE};
}
sub peers {
my $self = shift;
if (@_) { @{ $self->{PEERS} } = @_ }
return @{ $self->{PEERS} };
}
1; # require 나 use 를 성공시키기 위해서
오브젝트의 데이터에 접근하는 3가지의 메소드, name(), age(),
peers() 를
만들었습니다.
이것들은, 사실 모두 비슷한 것들입니다.
인수를 붙여 호출하면, 필드에 값을 설정하고, 인수가 없으면
그 필드에 저장된 값을 반환합니다.
해쉬의 키는 값을 의미합니다.
(미래를 생각하기 : 보다 좋은 생성자)
예를 들어, 이 시점에서 그 의미하는 바를 몰라도 있어도, 언젠가는 계승에
대해 고민할 것입니다.
(바라는 게 있다면, 지금 현재 계승을 안전하게 무시하고, 나중에 계승에
대해 고민할 수 도 있겠습니다).
곗으이, 모든 게 부드럽고 잘 움직이는 것은 보증하려면,
bless() 에 2개의 인수를
넘기지 않으면 안됩니다.
2번째의 인수는 클래스이지만, 리퍼런스를 bless하는 클래스입니다.
기본값으로는, (2번째의 인수를 생략하면) 2번째의 인수로써 자기자신의
클래스를 상정합니다.
그 대신에, (생성자의 첫번째 인수에) 넘겨지는 클래스를 사용합니다.
이렇게 하는 것으로, 생성자는 계승될 수 있습니다.
sub new {
my $class = shift;
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
bless ($self, $class);
return $self;
}
이것이, 생성자의 모든 것입니다.
이런 메소드들은, 오브젝트에 생명을 가지고, 제대로 되고, 작은
알지 못하는 값을 유저에게 넘기고, 다음 메소드의 호출에 사용됩니다.
(파괴자)
모든 얘기에는 시작이 있고, 끝이 있습니다.
오브젝트의 이야기의 시작에는 생성자가 있고, 오브젝트가
존재하기 시작할 때에는 명시적인 생성자가 호출됩니다.
오브젝트의 이야기의 끝은,
파괴자이지만, 이 메소드는,
오브젝트가 그 생명을 다했을 때에 암묵적으로 호출됩니다.
오브젝트마다 뒷정리의 코드가 파괴자 있고, 파괴자는
(Perl에서는) DESTROY 라고 불리지 않으면 안됩니다.
생성자는 임의의 이름인데 왜 파괴자는 임의적이지 않은 것일까?
그 이유는, 생성자는 명시적으로 호출되지만, 파괴자는 그렇지 않기 때문입니다.
파괴자의 호출은, Perl 의 가비지컬렉션 시스템 경유로 자동적으로 발생합니다.
때론 빠르지만, 어느 부분에서는 나태한 리퍼런스에 기반한 가비지컬렉션입니다.
호출해야할 것이 무엇인지 알 수 있도록, Perl 은 파괴자가 DESTROY 라고
이름지어지는 것을 주장합니다.
Perl 은 파괴자를 호출하는 적절한 시기를 생각하지만, 현재 언제 호출되는 가는
제대로 정의되어 있지 않습니다.
이 때문에, 파괴자는 그것이 언제 호출되는 가를 생각해야 되는 것은 아닙니다.
왜 DESTROY 는 모두 대문자일까?
Perl 은 때때로 관례에서 모든 대문자의 함수명을 사용합니다.
이 함수는, 어떤 방법으로 Perl 에 의해 자동적으로 호출될지도 모르는 것을
나타냅니다.
DESTROY 외에, 암묵적으로 호출되는 것에는 BEGIN 이나 END 나 AUTOLOAD,
거기에,
perltie 에 쓰여진, tie 된 오브젝트에 사용되는 모든
메소드등이 포함됩니다.
정말 좋은 오브젝트지향 프로그램 언어에서는, 유저는 파괴자가
호출되는 때를 신경쓰지 않습니다.
발생할 때에는, 단지 발생하는 것입니다.
어떤 가비지컬렉션도 가지지 않은, 낮은 레벨에의 언어에서는,
적절한 시기에 파괴자가 호출되는 것을 전혀 생각치도 않습니다.
그 때문에 프로그래머는 명시적으로 파괴자를 호출하고, 메모리나 상태의
뒷정리를 할 필요가 있습니다.
C++ 와는 달리, 오브젝트의 파괴자는 대부분, Perl 에서는
필요로 하지 않습니다.
그리고, 파괴자가 있을 때에도, 명시적 호출은 없습니다.
Person 클래스의 경우는 파괴자를 필요로 하지 않습니다.
이것은, 메모리의 할당 해제같은 단순한 문제는
Perl 이 알아서 해주기 때문입니다.
Perl 의 리퍼런스에 기반한, 가비지컬렉션이 움직이지 않는 유일한 상황은,
데이터 구조에 순환성이 있을 때입니다.
다음같은 것입니다:
$this->{WHATEVER} = $this;
프로그램이 메모리 릭을 하지 않는 것을 기대한다면, 이 경우에는, 자기참조를
수동으로 제거하지 않으면 안됩니다.
명백하게 에러를 발생시키기 쉬운 상태에서 이것은 단지 지금 할 수 있는
최고의 일입니다. 하지만, 프로그램이 끝날 때, 그 오브젝트의 파괴자가
모두 정식으로 호출되기에, 안전이 보증됩니다.
그 때문에 다음 일이 보증됩니다.
프로그램이 종료하지 않는 유일한 경우를 빼고는, 오브젝트가
최종적으로 적절하게 파괴합니다.
(다른 어플리케이션에, 심어진 Perl 을 실행하려면, 가비지컬렉션의 통과는
보통 보다 조금씩 빈번하게 - 스레드가 종료할 때에 - 발생합니다).
(다른 오브젝트 메소드)
지금까지 이야기한 메소드는 생성자 이외는 단순한 ``데이터 메소드''이고,
오브젝트에 축적된 데이터의 인터페이스입니다.
이 데이터들은 C++에서의 오브젝트의 데이터멤버와 조금 닮았습니다.
뭔지 모르고 보는 사람이 데이터로써, 오브젝트의 데이터멤버에 접근ㄹ 수 없는 것을
기대하고 있습니다.
그 대신에, 오브젝트의 데이터멤버에 데이터메소드 경유로,
간접적으로 접근해야합니다.
이 것은 Perl 에서, 중요한 룰입니다.
Perl 에서는, 오브젝트의 데이터로의 접근은, 메소드를 통해서
만
이루어져야 합니다.
Perl 은, 누군가가 어느 메소드를 사용할 수 있는 가의 제한을 둘 수 없습니다.
퍼블릭 과 프라이베이트 의 차이는 관례에 따른 는 것으로 구문에 따르지 않습니다.
(아마도,
변수로써의 데이터멤버에, 아래처럼 기술하는 Alias 모듈을
사용하지 않는 경우는).
메소드의 이름이 하나, 2개의 언더바로 시작되거나 끝나는 것을 본 적이 있을겁니다.
그 메소드가 그 클래스만, 그리고, 밀접한 그 사이에, 즉 서브클래스의
프라이베이트한 것을 나타냅니다.
하지만, 이 구별은 Perl 자신에 의해 강제되어 있지는 않습니다.
그런 행동을 하는 것은, 프로그래머에 달려있습니다.
메소드를 단순한 데이터에 접근하는 것만으로 한정하는 이유는 없습니다.
메소드는 아무것이라도 될수 있습니다.
키 포인트는, 메소드는 오브젝트나 클래스를 배경으로 호출되는 것입니다.
예를들어, 하나의 특별한 필드에서 값을 얻어오거나, 정의하거나 하는
이상의 것을 하는 오브젝트 메소드를 나타내봅시다.
sub exclaim {
my $self = shift;
return sprintf "Hi, I'm %s, age %d, working with %s",
$self->{NAME}, $self->{AGE}, join(", ", @{$self->{PEERS}});
}
또는, 다음과 같은 것:
sub happy_birthday {
my $self = shift;
return ++$self->{AGE};
}
다음과 같은 방법으로 한다고 하는 사람도 있을겁니다:
sub exclaim {
my $self = shift;
return sprintf "Hi, I'm %s, age %d, working with %s",
$self->name, $self->age, join(", ", $self->peers);
}
sub happy_birthday {
my $self = shift;
return $self->age( $self->age() + 1 );
}
하지만, 이 메소드들은 클래스 스스로 모두 실행할 수 있기에, 이것은
중요하지 않을 지도 모릅니다.
트레이드오프가 있습니다.
직접적인 해쉬의 접근은 그렇지 않은 경우보다도 빨리(실제로는 자릿수차이만큼
빨리), 문자열을 수정하고 싶은 때에는 보다 편리합니다.
하지만, 메소드(외부의 인터페이스)를 사용하면, 내부적으로
클래스를 사용하는 사람만이 아닌, 클래스를 만든 사람스스로가 데이터 표현을
수정할 수도 있습니다.
voltar para o topo
(클래스데이터)
``클래스데이터'' - 클래스의 각각의 오브젝트에 공통의 데이터아이템 - 이라는 것은
뭘까요? 뭐 때문에 클래스데이터가 필요할까요?
아마 Person 클래스에서는 살아 있는 사람들의 총수를 알아두고 싶겠죠?
$Person::Census 라고 불리는 글로벌 변수에서 그런 것이
가능합니다.
하지만, 그렇게 하는 유일한 이유는, 사람들이 직접 클래스데이터를
얻을 수 있도록 하고싶을 경우입니다.
$Person::Census 라고 말하는 것만으로, 그것을 건드릴 수가 있습니다.
당신의 생각으로는, 그런 것은 상관없을 지도 모르겠습니다.
혹시나, 변수가 가지고 나가는 것도 바라고 있을지도 모르겠습니다.
가지고 나간다면, (패키지의) 글로벌 변수가 아니면 안됩니다.
이것이 오브젝트지향적인 모듈이 아닌, 전통적인 모듈이면,
그렇게 하면 될겁니다.
클래스 변수를 패키지의 글로벌 변수로 하는 방법은, 대부분의 전통적인
모듈에서 기대하고 있습니다.
하지만, 이런 방법은, 일반적으로 대부분의 오브젝트 지향의 모듈에서는,
오히려 어리석은 것이라고 생각합니다.
오브젝트지향의 모듈에서는, 데이터를 보호하는 베일을 설치하고,
구성과 인터페이스를 분리합니다.
하지만, 오브젝트 데이터에 접근하는 오브젝트 메소드를
제공하는 것처럼, 클래스 데이터에 접근하는 클래스메소드를 제공합니다.
그래서, 아직 $Census 를 패키지의 글로벌 변수로 계속하는 것이,
가능하고, 다른 사람이 모듈의 계약을 지지하고, 그 때문에
그 구성을 건드리지 않으면 신뢰하는 것이
가능합니다.
꽤 트릭키하게,
perltie 에 기술되어 있듯, $Census 를 tie 된
오브젝트로 하는 것도 가능합니다.
하지만, 대개는 클래스데이터를 파일영역의 렉시컬변수로 하고 싶죠.
그렇게 하기위해서는, 파일 에 단순히 다음과 같은 것을 두면 됩니다:
my $Census = 0;
my() 의 영역은, 보통 선언된 블록이 끝날 때(이 경우에서는,
모든 파일이 필요하고, 사용될 때)가 기한이지만, Perl 의
렉시컬 변수의 깊은 동작은 그 영역 안에서 선언된 변수에
접근할 수 있는 사이에, 변수가 할당해제되지 않는 것을 보증합니다.
이 것은,
local() 에서, 일시적인 값을 주어진 글로벌 변수에서는,
움직이지 않습니다.
$Census 를 패키지의 글로벌 변수로 할것인가, 대신에,
파일 영역의 렉시컬 변수로 할 것인가에 상관없이,
Person::new() 생성자에, 다음같은 변경을 해야합니다.
sub new {
my $class = shift;
my $self = {};
$Census++;
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
bless ($self, $class);
return $self;
}
sub population {
return $Census;
}
이렇게 해서 물론, Person 이 없어질 때에, $Census 를 없애기위해,
파괴자가 필요합니다. 다음과 같이 합니다.
sub DESTROY { --$Census }
파괴자 안에서, 할당해제하기 위한 메모리가 없는 것을 어떻게 해서
알수 있을 까요?
그것은 Perl 스스로가 알아서 해줍니다.
대신에, CPAN에서 Class::Data::Inheritable 모듈을 사용할 수도 있습니다.
(클래스데이터에 접근하기)
클래스데이터를 항상 다루는 방법은, 정말 좋지 않은 것을 알 수 있습니다.
충분히 확장할 수 있는 룰은,
오브젝트 메소드에서, 직접 클래스데이터를
참조하지 않는다> 는 것입니다.
그렇지 않으면, 확장할 수 있고, 계승할 수 있는 클래스는 만들 수 없습니다.
오브젝트는, 모든 오퍼레이션을 위해, 랑데뷰 포인트에 없으면 안됩니다.
특히, 그 중에서도 오브젝트 메소드에서는, 글로벌 변수(클래스 데이터)는,
어떤 의미에서, 파생 클래스 안에서, ``다른'' 패키지에 있습니다.
Perl 에서는, 메소드는 메소드가 정의된 클래스 안의 컨텍스트에서
실행하는 것이고, 메소드가 움직인 오브젝트의 컨텍스트가
아닙니다.
그 때문에, 이름공간 - 메소드안에서, 눈에 보이는 패키지의 글로벌 변수의 -
은 계승과 관계없습니다.
그 이름공간을 얻을 수 있을까요? 아마도 무리입니다.
그럼 예를 들어보죠. 다른 클래스가 위에서 정의되어 있는(Person 클래스)
DESTROY 메소드를 (아마도 계승된) ``빌렸다''고 합시다.
이 다른 클래스의 오브젝트들이 파괴된다면, 오리지널 $Census 변수는
변화되게 됩니다.
새로운 클래스의 패키지의 이름공간의 $Census 가 아닙니다.
혹여나, 이것은 당신이 바라던 바일지도 모르겠지만, 십중팔구 다를 것입니다.
이것을 수정하는 방법이 있습니다.
해쉬 키 ``_CENSUS''로, 접근된 값의 데이터에 리퍼런스를 부여합니다.
왜 언더바가 붙느냐면, 아마도 최초의 언더바는 항상 C 프로그래머의
신기한 감각을 전하기 때문입니다. 이 필드가 특수하고, NAME , AGE, PEERS 처럼
퍼블릭한 데이터메소드가 아니라는 것을 생각하면, 단순한 기억을 도와주는
궁리입니다.
(이 코드는, strict 프라그마를 붙여서 개발하고 있기에,
perl 5.004 보다 낡은 버젼이라면 필드 이름을 인용하지 않으면 안될겁니다)
sub new {
my $class = shift;
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
# "private" data
$self->{"_CENSUS"} = \$Census;
bless ($self, $class);
++ ${ $self->{"_CENSUS"} };
return $self;
}
sub population {
my $self = shift;
if (ref $self) {
return ${ $self->{"_CENSUS"} };
} else {
return $Census;
}
}
sub DESTROY {
my $self = shift;
-- ${ $self->{"_CENSUS"} };
}
(메소드 디버깅)
클래스에 디버그의 매커니즘이 있는 것은, 일반적입니다.
예를듦녀, 오브젝트가 만들어졌는지 파괴되었을 때에 디버그 정보를 보고싶겠죠?
그렇게 하기위해, 디버그의 값을 파일영역의 렉시컬 변수에 넣습니다.
이 때문에, 경고과 치명적인 메시지를 내는 표준의 Carp 모듈을 호출할 겁니다.
이 방법에 의해, 자기자신의 메시지 대신에 caller 의 파일이름과 줄 수와 함께
경고가 나옵니다.
만약, 자기자신의 관점에서, 경고나 치명적인 메시지를 보고 싶다면,
croak() 대신에 그냥
die() 나
warn() 을 직접 사용하면 됩니다.
use Carp;
my $Debugging = 0;
그럼, 새로운 클래스메소드에 변수의 접근을 넣어봅다.
sub debug {
my $class = shift;
if (ref $class) { confess "Class method called as object method" }
unless (@_ == 1) { confess "usage: CLASSNAME->debug(level)" }
$Debugging = shift;
}
그럼, DESTROY 를 만져서, 소멸하려는 오브젝트가 사라질 때에
조금 경고를 내도록 하겠습니다.
sub DESTROY {
my $self = shift;
if ($Debugging) { carp "Destroying $self " . $self->name }
-- ${ $self->{"_CENSUS"} };
}
혹시나, 오브젝트마다 디버상태를 가지고 싶을 지도 모르겠습니다.
양쪽 모두 할 수 있는 방법이 있습니다.
Person->debug(1); # entire class
$him->debug(1); # just this object
이렇게 하기 위해서는, 디버그메소드는 ``이항'' 메소드가 아니면 안됩니다.
클래스
와 오브젝트 로 움직이는 메소드인 겁니다.
이 때문에
debug() 와 DESTROY 메소드를 다음처럼 수정합니다.
sub debug {
my $self = shift;
confess "usage: thing->debug(level)" unless @_ == 1;
my $level = shift;
if (ref($self)) {
$self->{"_DEBUG"} = $level; # just myself
} else {
$Debugging = $level; # whole class
}
}
sub DESTROY {
my $self = shift;
if ($Debugging || $self->{"_DEBUG"}) {
carp "Destroying $self " . $self->name;
}
-- ${ $self->{"_CENSUS"} };
}
(Employee 라고 불리는) 파생클래스가 Person 기본 클래스에서 메소드를
계승하면, 뭐가 발생할까요?
Employee->debug() 는 클래스메소드로써 호출된 경우,
$Employee::Debugging 이 아니라, $Person::Debugging 을 조작합니다.
(클래스 파괴자)
오브젝트의 파괴자는, 각각의 다른 오브젝트마다 죽음을 취급합니다.
그러나, 클래스 전체의 셧다운 - 이것은 현재 프로그램이 종료한 때에 발생합니다 -
조금 삭제가 필요한 때도 있습니다.
그런
클래스 파괴자 를 만들기 위해,
클래스 패키지 안에, END 라고 이름지어진 함수를 만듭니다.
END 의 움직임은 전통적인 모듈의 END 함수와 꽤 비슷합니다.
프로그램이 실행되지 않았는지 얻을 수 없는 신호로 죽어,
프로그램이 종료한 때에 언제라도 호출됩니다.
sub END {
if ($Debugging) {
print "All persons are going away now.\n";
}
}
프로그램이 종료한 때, 모든 클래스 파괴자(END 함수)는 ,
클래스가 로드되는 것과 반대의 순서(LIFO 순서)로 호출됩니다.
(인터페이스를 문서화)
그리고, 문서가 있습니다: 지금까지, 이 Person 클래스의
구성 을
보여주고 있습니다.
Person 클래스의
인터페이스 는, Person 클래스의 문서가 됩니다.
보통, 이것은 같은 파일에 pod(``plain old documentation'') 의
포맷으로 문서를 두는 것을 의미합니다.
Person 의 예제에서, Person.pm 파일의 안에, 아래에 계속할 문서를 배치합니다.
예를들어, 이 문서가 대부분 코드로 보여도, 다른것입니다.
이것은 문서가 심어져있는 것이고, pod2man 이나, pod2html 나, pod2text
프로그램에서 사용됩니다.
Perl 의 컴파일러는 pod 를 완전 무시합니.
번역자가 코드를 무시하는 것과 같습니다.
약식의 인터페이스를 기술하는 pod 의 예제입니다:
=head1 NAME
Person - class to implement people
=head1 SYNOPSIS
use Person;
#################
# class methods #
#################
$ob = Person->new;
$count = Person->population;
#######################
# object data methods #
#######################
### get versions ###
$who = $ob->name;
$years = $ob->age;
@pals = $ob->peers;
### set versions ###
$ob->name("Jason");
$ob->age(23);
$ob->peers( "Norbert", "Rhys", "Phineas" );
########################
# other object methods #
########################
$phrase = $ob->exclaim;
$ob->happy_birthday;
=head1 DESCRIPTION
The Person class implements dah dee dah dee dah....
인터페페이스 대 구성의 문제가 있습니다.
모듈을 열고, 인터페이스의 계약의 뒤에 있는, 안전하게 키를 잠군,
모든 프라이베이트한 것에 빛나는 작은 것을 만지는 프로그래머에게는,
보증이 통하지 않습니다. 그들의 운명을 걱정하지 않아도 됩니다.
voltar para o topo
(집약)
나중에, 보다 좋은 이름을 구성하기 위해, 클래스를 바꾸고 싶어졌다고 합시다.
아마, 첫번째 이름(그 사람의 종교에 상관없이 크리스챤 네임이라고 부릅니다)과
가족 이름(이름자라고 불리는) 모두 그것에 더해,
닉네임이라는 칭호를 지원하고 싶다고 하겠습니다.
만약 Person 클래스를 사용하는 사람이 이 문서에 쓰인 인터페이스를
통해서 적절히 접근하면, 단순히 그 아래에 있는 구성을 변경할 수 있습니다.
만약 그렇지 않으면, Person 클래스를 사용하는 사람은 지게됩니다.
계약을 부수고, 보증을 잃어버리거나 합니다.
다음처럼 해서, 다른 클래스를 만듭니다.
이 클래스는, Fullname 이라는 클래스입니다.
Fullname 클래스라는 것은 어떤 것일까요?
이 질문에 답하기위해, 맨처음에 그것을 어떻게 사용하고 싶은 가를
파악해야합니다.
Fullname 클래스는 다음처럼 사용합니다.
$him = Person->new();
$him->fullname->title("St");
$him->fullname->christian("Thomas");
$him->fullname->surname("Aquinas");
$him->fullname->nickname("Tommy");
printf "His normal name is %s\n", $him->name;
printf "But his real name is %s\n", $him->fullname->as_string;
좋습니다. 이렇게 하기 위해 Person::new() 를 풀네임의 필드를 지원도록,
다음과 같이 바꾸었습니다:
sub new {
my $class = shift;
my $self = {};
$self->{FULLNAME} = Fullname->new();
$self->{AGE} = undef;
$self->{PEERS} = [];
$self->{"_CENSUS"} = \$Census;
bless ($self, $class);
++ ${ $self->{"_CENSUS"} };
return $self;
}
sub fullname {
my $self = shift;
return $self->{FULLNAME};
}
그리고, 낡은 코드를 지원하기 위해, Person::name() 을 다음처럼
정의합니다 :
sub name {
my $self = shift;
return $self->{FULLNAME}->nickname(@_)
|| $self->{FULLNAME}->christian(@_);
}
Fullname 클래스가 있습니다.
데이터ㄹ드를 가진 것에 해쉬리퍼런스를 사용한 것과 같은 테크닉을 사용해서,
데이터 필드에 접근하는 것에 적절한 이름으로 메소드를 사용합니다:
package Fullname;
use strict;
sub new {
my $class = shift;
my $self = {
TITLE => undef,
CHRISTIAN => undef,
SURNAME => undef,
NICK => undef,
};
bless ($self, $class);
return $self;
}
sub christian {
my $self = shift;
if (@_) { $self->{CHRISTIAN} = shift }
return $self->{CHRISTIAN};
}
sub surname {
my $self = shift;
if (@_) { $self->{SURNAME} = shift }
return $self->{SURNAME};
}
sub nickname {
my $self = shift;
if (@_) { $self->{NICK} = shift }
return $self->{NICK};
}
sub title {
my $self = shift;
if (@_) { $self->{TITLE} = shift }
return $self->{TITLE};
}
sub as_string {
my $self = shift;
my $name = join(" ", @$self{'CHRISTIAN', 'SURNAME'});
if ($self->{TITLE}) {
$name = $self->{TITLE} . " " . $name;
}
return $name;
}
1;
마지막으로, 테스트프로그램입니다:
#!/usr/bin/perl -w
use strict;
use Person;
sub END { show_census() }
sub show_census () {
printf "Current population: %d\n", Person->population;
}
Person->debug(1);
show_census();
my $him = Person->new();
$him->fullname->christian("Thomas");
$him->fullname->surname("Aquinas");
$him->fullname->nickname("Tommy");
$him->fullname->title("St");
$him->age(1);
printf "%s is really %s.\n", $him->name, $him->fullname->as_string;
printf "%s's age: %d.\n", $him->name, $him->age;
$him->happy_birthday;
printf "%s's age: %d.\n", $him->name, $him->age;
show_census();
voltar para o topo
(계승)
오브젝트지향프로그래밍의 시스템은, 모두 계승을 지원합니다.
계승은, 하나의 클래스가 다른 클래스의 위에 얹어진다는 것을 의미합ㄴ디ㅏ.
그래서, 같은 코드를 반복하고, 반복해서 쓰지않아도 됩니다.
즉, 소프트웨어의 재이용입니다.
그리고, 부정 - 프로그램의 중요한 미덕 - 에 관련있습니다.
(전통적인 모듈의 import/export 매커니즘도 코드의 재 이용형태입니다.
하지만, 오브젝트모듈에서 보이는, 진정한 계승보다도 단순한 것입니다)
계승의 문법은, 때로 언어의 핵심에 포함되어 있는 것이 있다면,
그렇지 않은 때도 있습니다.
Perl 은, 하나의 클래스(또는 복수의 클래스)에서, 계승하는 것에 특별한 문법은
없습니다.
그 대신에 의미론적으로 모두 엄격합니다.
각각의 패키지에는 @ISA 라고 불리는 변수가 있고, @ISA 는 (메소드) 계승을
관리하는 것입니다.
만약 오브젝트가 클래스로, 메소드를 호출하려고 해서, 그 메소드가
그 오브젝트의 패키지에는 보이지 않으면, Perl은 발견할 수 없는
메소드를 검색하는 동안에 @ISA 를 보고 다른 패키지를 찾으러 갑니다.
특별한 패키지마다 Exporter로 나누는 변수(@EXPORT, @EXPORT_OK,
@EXPORT_FAIL, %EXPORT_TAGS, $VERSION)처럼
@ISA 배열은 패키지영역의 글로벌 변수가
아니면 안되고,
my() 로 만들어진 파일영역의 렉시컬 변수여서는
안됩니다.
대부분의 클래스에서, @ISA 배열에는 하나밖에 없습니다.
이 것은 ``단순 계승''이라고 불리는 것입니다.
생략해서 SI라고도 불립니다.
이 클래스에 대해서, 생각해 봅시다:
package Employee;
use Person;
@ISA = ("Person");
1;
많지 않죠? 지금, 이 Employee 클래스가 하는 것은 다른 클래스를 로드하는 것과,
필요하다면, 다른 클래스에서 메소드를 계승하는 것을 시작하는 것뿐입니다.
Employee 클래스에는, 각자의 메소드를 주어서는 안됩니다.
하지만, Employee 클래스에 Person 클래스같은 행동을 합니다.
이런 빈 클래스를 만드는 것은 ``빈 서브클래스테스트''라고 불립니다.
이것ㅡㄴ 기본 클래스에서 계승하는 것 이외에는, 아무것도 하지 않는 파생클래스를
만드는 것입니다. 오리지널의 기본 클래스는 적절히 설계되어 있으면,
새로운 파생클래스는, 낡은 것의 삽입식의 치환으로써 사용됩니다.
이것이 의미하는 것은 다음과 같은 프로그램을 쓸 수 있다는 것입니다:
use Employee;
my $empl = Employee->new();
$empl->name("Jason");
$empl->age(23);
printf "%s is age %d.\n", $empl->name, $empl->age;
適切な設計によって、そうできます。
適切な設計とは、常に
bless() に、2 つの引数を与え、グローバルなデータに
直接アクセスするのを避け、何も export しない設計のことです。
上の方で定義している Person::new() 関数を見返したら、そうするように
注意しています。
コンストラクタで使われるパッケージデータが少しありますが、
パッケージデータへのリファレンスが、オブジェクト自身に、蓄えられており、
他の全てのメソッドはそのリファレンスを通して、パッケージデータに
アクセスします。
それで、よしとすべきです。
Person::new() 関数で、意図したこと -- 実際にはメソッドではないですか?
たぶん、原則的には、メソッドです。
メソッドは、ちょうど、その最初の引数に、クラスの名前(パッケージ)か、
オブジェクト(bless されたリファレンス)を期待する関数です。
Person::new()は、結局、
Person->new() メソッドと
Employee->new()
メソッドの両方が呼んでいる関数です。
メソッド呼び出しが関数呼び出しによく似ていながら、本当は、まったく
同じではなく、この 2 つを同じものとして扱うなら、すぐに壊れたプラグラムしか
残されないことを理解してください。
第一に、現実に、基礎となっている呼び出しの慣例が違っています:
メソッド呼び出しは、特別な引数を得ます。
第二に、関数呼び出しは継承をしませんが、メソッド呼び出しは継承をします。
メソッド呼出 結果として、関数呼び出し
----------- ------------------------
Person->new() Person::new("Person")
Employee->new() Person::new("Employee")
ですので、メソッドを呼ぼうとしているときに関数呼び出しを使わないでください。
従業員が、ただの、Person だったら、それはあまり面白くありません。
さあ、他のメソッドを追加しましょう。
従業員に、データフィールドを設けて、従業員の給与や、従業員 ID や、入社日に
アクセスしましょう。
これらのオブジェクトのデータを取得するような、ほとんど同じメソッドたちを
作るのにちょっと飽きたとしても、落胆しないでください。
後で、こういった作業を短縮するための、いくつかの種々の便利な
メカニズムについて説明します。
その逆に、単純なやりかたは次のものです:
sub salary {
my $self = shift;
if (@_) { $self->{SALARY} = shift }
return $self->{SALARY};
}
sub id_number {
my $self = shift;
if (@_) { $self->{ID} = shift }
return $self->{ID};
}
sub start_date {
my $self = shift;
if (@_) { $self->{START_DATE} = shift }
return $self->{START_DATE};
}
(メソッドのオーバーライド)
派生クラスとその基底クラスの両方が同じ名前のメソッドを定義したら、
何が起きるでしょうか?
たぶん、そのメソッドは派生クラスのもの(訳註:version)を得ます。
例えば、従業員で、peers()メソッドを呼ぶと、(訳註:Personとは、)少々違った
動きをさせたいとしましょう。
同僚の名前のリストをただ返す代わりに、ちょっと違った文字列を返しましょう。
次のようにします:
$empl->peers("Peter", "Paul", "Mary");
printf "His peers are: %s\n", join(", ", $empl->peers);
次のようになります:
His peers are: PEON=PETER, PEON=PAUL, PEON=MARY
こうするためには、単に、この定義を Employee.pm ファイルに加えるだけです。
sub peers {
my $self = shift;
if (@_) { @{ $self->{PEERS} } = @_ }
return map { "PEON=\U$_" } @{ $self->{PEERS} };
}
ここで、ちょうど、
多態性(polymorphism) として広く知られている、大げさな
コンセプトのデモンストレーションをしています。
存在するオブジェクトの形と振るまいを担い、われわれ自身の目的に
適応するように、存在するオブジェクトを変更します。
これは、不精の形です。
(polymorph させることは、魔法使いが、あなたを、蛙に
見せようと決めるときに起こることです)。
しばしば、派生クラス(``サブクラス''としても知られるクラス)のもの
(訳註:version)とその基底クラス(``スーパークラス''としても知られるクラス)の
もの(訳註:version)も同じように両方をきっかけをメソッドに呼ばせたいでしょう。
実際に、コンストラクタとデストラクタは、そうすることを望んでいそうですし、
先にみた、debug() メソッドでは、十中八九、そうだとうなずけます。
そうするために、Employee.pm に次のものを加えます:
use Carp;
my $Debugging = 0;
sub debug {
my $self = shift;
confess "usage: thing->debug(level)" unless @_ == 1;
my $level = shift;
if (ref($self)) {
$self->{"_DEBUG"} = $level;
} else {
$Debugging = $level; # whole class
}
Person::debug($self, $Debugging); # don't really do this
}
お分かりのように、(訳註:Employee から)向きを変えて、Person パッケージの
debug() 関数を呼びます。
ですが、良い設計にとっては、こうするのは、ひじょーに、壊れやすいものです。
Person が
debug() 関数を持っておらず、
その debug() メソッドを
他のところから継承していたら、どうなるでしょう?
次のようにする方が、良いです。
Person->debug($Debugging);
ですが、まだ、ハードコードしすぎです。
次のようにする方が、良いです。
$self->Person::debug($Debugging);
これは、Person 内で、debug() メソッドを探し始める楽しいやりかたです。
この作戦は、オーバーライドされたクラスメソッドよりも、
オーバーライドされたオブジェクトメソッドで、よく見ます。
少しのものがまだ、あります。
スーパークラスの名前をハードコードしています。
継承するクラスを変えるか、他のものを加えたら、このことは、特に、悪いです。
幸運なことに、仮名クラスの SUPER が、ここで、助けになります。
$self->SUPER::debug($Debugging);
こうすると、自分のクラスの @ISA をのぞき始ます。
これは、メソッドコール
内 から意味をなすだけです。
他のどこからも、SUPER:: の何にも、アクセスしようとしないでください。
というのは、上書きされたメソッド呼出の外側には存在しないからです。
Note that
SUPER refers to the superclass of
the current package,
not to the superclass of
$self.
(TBT)
事態は、ちょっと複雑になってきています。
してはならないことをしたでしょうか?
これまでのように、まともなクラスかどうかをテストする一つの方法は、
空のサブクラスを経由するテストです。
すでに、チェックしている Employee クラスがあるので、
Employee から、新しい空のサブクラスを生成できます。
次のものです:
package Boss;
use Employee; # :-)
@ISA = qw(Employee);
テストプログラムです:
#!/usr/bin/perl -w
use strict;
use Boss;
Boss->debug(1);
my $boss = Boss->new();
$boss->fullname->title("Don");
$boss->fullname->surname("Pichon Alvarez");
$boss->fullname->christian("Federico Jesus");
$boss->fullname->nickname("Fred");
$boss->age(47);
$boss->peers("Frank", "Felipe", "Faust");
printf "%s is age %d.\n", $boss->fullname->as_string, $boss->age;
printf "His peers are: %s\n", join(", ", $boss->peers);
実行すると、まだ、大丈夫です。
素敵なフォーマットで、デバッガーで 'x'コマンドを働かせる方法のように、
オブジェクトをダンプしたいなら、CPAN から、Data::Dumpler モジュールを
次のように使うことが出来ます:
use Data::Dumper;
print "Here's the boss:\n";
print Dumper($boss);
次のようなものを見せます:
Here's the boss:
$VAR1 = bless( {
_CENSUS => \1,
FULLNAME => bless( {
TITLE => 'Don',
SURNAME => 'Pichon Alvarez',
NICK => 'Fred',
CHRISTIAN => 'Federico Jesus'
}, 'Fullname' ),
AGE => 47,
PEERS => [
'Frank',
'Felipe',
'Faust'
]
}, 'Boss' );
ふーむ、何かがなくなっています。
給与、入社日、ID フィールドはどうでしょうか?
たぶん、それらに、何も、undef さえもセットしていません。
そのため、ハッシュのキーに、それらが見えなかったのです。
Employee クラスは、それ自身の
new() メソッドがありません。
Person の
new() メソッドは、Employee について知りません。
(また、そうすべきでもありません:適切なオブジェクト指向設計はサブクラスが
そのすぐ上のスーパークラスについて知っていてもよいと指示しています。
ですが、その逆は同様ではありません)。
では、Employee::new() を次のように、しましょう:
sub new {
my $class = shift;
my $self = $class->SUPER::new();
$self->{SALARY} = undef;
$self->{ID} = undef;
$self->{START_DATE} = undef;
bless ($self, $class); # reconsecrate
return $self;
}
Employee か Boss オブジェクトをダンプしたら、今度は、新しいフィールドが
そこに現れるでしょう。
(多重継承)
よし。
初心者もオブジェクト指向のエキスパートも混乱させる危険をおかしても、白状する
時がきました。
Perl のオブジェクトシステムは、多重継承、または、略してMIとして知られる、
賛否両論の考えを含んでいます。
これが意味するのは、次のことで全てです。
ただ一つの親クラス - 今度は、それ自身が親クラスやそのほかを持っている - を
持つのではなく、2 つ以上の親クラスを持つことが出来ることです。
多重継承の使うことで、トラブルに見まわれることもありますが、
C++ のようなあいまいなオブジェクト指向言語の Perl では、うまくいけば、たいした
問題にはならないこともあります。
多重継承のやりかたは、実際、非常に簡単です:@ISA 配列に二つ以上の
パッケージ名を置けばいいだけです。
Perlが、オブジェクトのために、メソッドを見つけに行く時がきたら、
順番に @ISA のパッケージをそれそれ見ます。
えっと、かなり。
完全に再帰的で、デフォルトでは
深さ優先です(see
mro for alternate method resolution orders)。
次のような @ISA 配列の組合せを考えてください:
(TBT)
@First::ISA = qw( Alpha );
@Second::ISA = qw( Beta );
@Third::ISA = qw( First Second );
Third クラスのオブジェクトがあるとすると、
my $ob = Third->new();
$ob->spin();
spin() メソッド(または、さらに言えば、new())をどのように見つけるのでしょうか?
検索は、深さ優先なので、クラスは次の順番で調べられます:
Third First Alpha Second Beta の順です。
実際には、多重継承が使われているクラスモジュールは、ほとんどみかけません。
ほとんどいつも、多重継承の上の別のものの中の、
単純な一つのクラスを積んだコンテナ船を選びます。
Person オブジェクトは、Fullname オブジェクトを
積んだ
オブジェクトだからです。
それが一つで
あった というわけではありません。
(訳註:謎)
しかしながら、Perl での多重継承が見境のない、一つの特別な領域があります:
他のクラスのクラスメソッドを借りて来ることです。
このことは、幾分普及しており、
バンドルされている``オブジェクトのない''クラスで特にそうです。
Exporter や、DynaLoader や、AutoLoader や、SelfLoader がそうです。
これらのクラスは、コンストラクタを提供しません。
これらのクラスは、(訳註:他のクラスから)クラスメソッドを
継承するかもしれないので、存在しています。
たとえば、POSIX モジュールの @ISA は次のようになっています:
package POSIX;
@ISA = qw(Exporter DynaLoader);
POSIX モジュールは、実際には、オブジェクトモジュールではありません。
ですが、Exporter も
DynaLoader? も、そうです。
Exporderと、DynaLoader クラスは、クラスメソッドの振るまいを POSIX に、
貸しているだけです。
なぜ、オブジェクトメソッドに、多重継承が使われないのでしょうか?
理由の一つは、複雑な副作用がありうるからです。
まず一つ例をあげると、継承のグラフ(ツリーではなく)が、同じ基底クラスに
もどって集中します。
Perlは、再帰的な継承に対して防御していますが、単に、共通の祖先を通して、
お互いに関連している親を持つことは、近親相姦に思えても、禁止されていません。
もし、先ほど見た Third クラスで、new() メソッドが、Third クラスの 2 つの
親クラスで、オーバーライドされたコンストラクタを両方呼ぶとしたら、
どうなるんでしょう?
SUPER 表記は、最初の一つしか見つけません。
Alpha と Beta クラスの両方が、共通の祖先、たとえば Nought クラスを
持っていたらどうでしょうか?
オーバーライドされたメソッドを呼ぶ継承ツリーを登り続けるなら、結局、
Nought::new() を二度呼ぶことになります。
たぶん、それは、悪い考えでしょう。
(UNIVERSAL: 全てのオブジェクトの根っこ)
全てのオブジェクトが、いくつかの根本的な基底クラスに根付いていたら、
便利でしょうか?
すべてのオブジェクトに共通のメソッドを与えるのに、それぞれの @ISA に
クラスを加えずにできる方法です。
それで、そうできることがわかります。
それを見ることはありませんが、Perl は、それとなく、決定的に、
@ISA の終わりに、特別な要素があることを想定しています:UNIVERSAL クラスです。
5.003 では、UNIVERSAL クラスにあらかじめ定義されているメソッドは、
ありませんでしたが、UNIVERSAL クラスにいれたいようなものは、なんでも、
置くことが出来ました。
しかし、5.004 (または、5.003_08 のような、いくつかの破棄的なリリース)より、
UNIVERSAL には、既にいくつかのメソッドがあります。
これらのメソッドは、Perl バイナリに組み込まれており、そのため、それらを
ロードするのに、余計な時間はかかりません。
あらかじめ定義されているメソッドは、isa() 、can()、VERSION() です。
isa() は、オブジェクトかクラスが階層構造を横断することなく、
別のクラス``である''かどうかを教えます。
$has_io = $fd->isa("IO::Handle");
$itza_handle = IO::Socket->isa("IO::Handle");
can() メソッドは、オブジェクトかクラスに対して呼ばれて、その文字列の引数が
そのクラスで、呼ぶことの出来るメソッドかどうかを帰って報告します。
実際、そのメソッドに関数のリファレンスを返します:
$his_print_method = $obj->can('as_string');
最後に、VERSION メソッドは、クラス(または、オブジェクトのクラス)が、
$VERSION と呼ばれるパッケージのグローバル変数が十分に高いかどうかを
チェックします。
次のように:
Some_Module->VERSION(3.0);
$his_vers = $ob->VERSION();
しかし、ふつう、VERSION を自分自身には呼びません。
(次のことを覚えておいてください。
すべての大文字の関数名は、Perl の慣例で、その関数が、なんらかの方法で、
Perl によって自動的に使われるだろうということを、示しています。)
このケースでは、次のようにすると、同じことが起こります:
use Some_Module 3.0;
上に説明した Person クラスでバージョンのチェックを加えたいなら、
Person.pm に、次のことを加えるだけです:
our $VERSION = '1.1';
そして、Employee.pm で、次のようにできます:
use Person 1.1;
このことで、少なくともそのバージョン番号か、それより高いものが利用可能だと
確かめます。
正確なバージョン番号でのロードなので、同じではありません。
現在のところ、共同に作用する複数のバージョンのモジュールの
インストレーションのためには、どんなメカニズムもありません。
嘆かわしい。
(より深い UNIVERSAL の詳細)
It is also valid (though perhaps unwise in most cases) to put other
packages' names in @UNIVERSAL::ISA. These packages will also be
implicitly inherited by all classes, just as UNIVERSAL itself is.
However, neither UNIVERSAL nor any of its parents from the @ISA tree
are explicit base classes of all objects. To clarify, given the
following:
(TBT)
@UNIVERSAL::ISA = ('REALLYUNIVERSAL');
package REALLYUNIVERSAL;
sub special_method { return "123" }
package Foo;
sub normal_method { return "321" }
Calling Foo->
special_method() will return ``123'', but calling
Foo->
isa('REALLYUNIVERSAL') or Foo->
isa('UNIVERSAL') will return
false.
(TBT)
If your class is using an alternate mro like C3 (see
mro), method resolution within UNIVERSAL / @UNIVERSAL::ISA will
still occur in the default depth-first left-to-right manner,
after the class's C3 mro is exhausted.
(TBT)
All of the above is made more intuitive by realizing what really
happens during method lookup, which is roughly like this
ugly pseudo-code:
(TBT)
get_mro(class) {
# recurses down the @ISA's starting at class,
# builds a single linear array of all
# classes to search in the appropriate order.
# The method resolution order (mro) to use
# for the ordering is whichever mro "class"
# has set on it (either default (depth first
# l-to-r) or C3 ordering).
# The first entry in the list is the class
# itself.
}
find_method(class, methname) {
foreach $class (get_mro(class)) {
if($class->has_method(methname)) {
return ref_to($class->$methname);
}
}
foreach $class (get_mro(UNIVERSAL)) {
if($class->has_method(methname)) {
return ref_to($class->$methname);
}
}
return undef;
}
voltar para o topo
(ハッシュに代わるオブジェクトの表現)
オブジェクトはハッシュリファレンスとして実装される必要はありません。
オブジェクトは、うまく bless されるリファレンスでありさえすれば、どんな
種類のリファレンスでも可能です。
このことは、スカラーリファレンスでも、配列リファレンスでも、
コードのリファレンスでも、格好の的となるということです。
スカラーは、オブジェクトがたった一つの値しか持たないなら、うまくいきます。
配列は、ほとんどのケースで、うまくいきます。
ですが、継承を少々危うくします。
派生クラスのために、新しい索引を作り上げなければならないからです。
(オブジェクトとして配列を)
クラスを使う人が、契約を支持し、公示されたインターフェースに我慢するなら、
その基礎を成すインターフェースを、そうしたければ変えることが出来ます。
同じインターフェースの仕様に合う、他の実装です。
さて、オブジェクトを表現するのに、ハッシュリファレンスの代わりに、
配列リファレンスを使いましょう。
package Person;
use strict;
my($NAME, $AGE, $PEERS) = ( 0 .. 2 );
############################################
## オブジェクトコンストラクタ (配列版) ##
############################################
sub new {
my $self = [];
$self->[$NAME] = undef; # this is unnecessary
$self->[$AGE] = undef; # as is this
$self->[$PEERS] = []; # but this isn't, really
bless($self);
return $self;
}
sub name {
my $self = shift;
if (@_) { $self->[$NAME] = shift }
return $self->[$NAME];
}
sub age {
my $self = shift;
if (@_) { $self->[$AGE] = shift }
return $self->[$AGE];
}
sub peers {
my $self = shift;
if (@_) { @{ $self->[$PEERS] } = @_ }
return @{ $self->[$PEERS] };
}
1; # so the require or use succeeds
配列アクセスはハッシュアクセスより、だいぶ速いと思うかもしれません。
ですが、この2つは、実際は、類似のものです。
配列は、
ちょっと速い。
ですが、10か、50パーセントも速くはありません。
たとえ、上の $AGE のような文字の変数を、1 のような数字に置き換えても、です。
二つのアプローチの大きな違いは、メモリの使用で見つけられます。
ハッシュでの表現は、配列での表現よりもメモリを多く消費します。
値と同じように、キーにも、メモリを割り当てなければならないからです。
しかし、そのことは、本当に、そんなに悪いことではありません。
特に、5.004からは、ハッシュにキーを与えるために、メモリは一度
割り当てられるだけです。
いくつのハッシュがキーを持つのかは問題ではありません。
今後、この2つの違いは、より効率的な基礎となる表現が工夫されるにつれ、
あいまいに消滅していくだろうと、期待されています。
まだ、スピード(と、メモリで、幾分大きくなるところ)で、ちょっと
優れていることは、プログラマに、単純なクラスのために配列表現を
選ばせるのに十分です。
やはり、拡張性にちょっとした問題があります。
後年、サブクラスを作ろうとと思ったときに、
ハッシュがちょうどうまく働くことがわかるでしょうから。
(オブジェクトとしてクロージャを)
オブジェクトを表現するのに、コードリファレンスを使うことは、魅力ある将来性を
与えます。
世界中で唯一人しか、オブジェクトのデータを見ることが出来ない、
新しい匿名の(クロージャ)関数を作ることが出来ます。
というのは、データをレキシカルにしか見えない無名ハッシュに置くからです。
つまり、クロージャを作り、blessし、オブジェクトとして返すからです。
このオブジェクトメソッドは、一変して、普通のサブルーチン呼出として
クロージャを呼び、影響を与えたいフィールドに、それを渡します。
(はい、二重の関数呼び出しは、遅いです。
ですが、もし、速さを求めるのなら、オブジェクトを全く使わなければいいんです。
そうでしょ?

使いかたは、前と似ています;
use Person;
$him = Person->new();
$him->name("Jason");
$him->age(23);
$him->peers( [ "Norbert", "Rhys", "Phineas" ] );
printf "%s is %d years old.\n", $him->name, $him->age;
print "His peers are: ", join(", ", @{$him->peers}), "\n";
ですが、実装は、根本的に、たぶん、圧倒的なやりかたで、違っています。
package Person;
sub new {
my $class = shift;
my $self = {
NAME => undef,
AGE => undef,
PEERS => [],
};
my $closure = sub {
my $field = shift;
if (@_) { $self->{$field} = shift }
return $self->{$field};
};
bless($closure, $class);
return $closure;
}
sub name { &{ $_[0] }("NAME", @_[ 1 .. $#_ ] ) }
sub age { &{ $_[0] }("AGE", @_[ 1 .. $#_ ] ) }
sub peers { &{ $_[0] }("PEERS", @_[ 1 .. $#_ ] ) }
1;
(訳註:圧倒的に違うのは、)オブジェクトがコードリファレンスの背後に
隠されているからです。
このことはちょっとミステリアスです。
関数的なプログラム言語よりも、標準的な手続き型や、オブジェクトベースの
プログラミング言語の方が、そういった背景が、クロージャの由来するところから、
よりしっかりと定着しているからです(訳註:?)。
new() メソッドで作られて、返されたオブジェクトは、今まで見てきたような、
データのリファレンスではありません。
オブジェクトは、匿名のコードリファレンスであり、内部に、特定の
バージョン(レキシカルにバインドしており、例示している(訳註:?))の
- プライベートな変数 $selfに蓄えられた - オブジェクトデータにアクセスする
コードを持っています。
それは、毎回、同じ関数であるにもかかわらず、違ったバージョンの $self を
含みます。
$him->name("Jason") のようなメソッドが呼ばれると、暗黙の 0 番目の
引数がデフォルトとして呼び出されます -- 全てのメソッド呼出と一緒のように。
ですが、この場合では、その引数は先ほどのコードリファレンスです(C++ での
ポインタ関数のような何か、ですが、レキシカルな変数に深く
バインドされています)。
コードリファレンスを呼ぶこと以上に、コードリファレンスですることは
多くはありません。
つまり、このときするのは、
&{$_[0]} とすることだけです。
これは、単なる普通の関数呼び出しであり、メソッド呼び出しではありません。
最初の引数は、文字列 ``NAME'' であり、残りの引数は、メソッドに
渡されているもの全てです。
new() で作られたクロージャの内側で実行されると、$self ハッシュリファレンスは
突然に見えるようになります。
クロージャは、その最初の引数(このケースでは、'NAME' です。
name() メソッドがそれを渡したので)をつかんで、その文字を、ユニークな
バージョンの $self のプライベートで隠されたハッシュへの添字に使います。
この世で、誰も、実行されているメソッドの外側にいるものには、
この隠されたデータに近付けることを許されていません。
たぶん、ほとんどない。
デバッガを使っているプログラムを通すステップを選び、そのメソッドにいる間、
そのかけらを見つけることが
できます。
ですが、他の全ての人には、運がありません。
もし、このことが Scheme なみなさんを興奮させなければ、何が
興奮させるのかわかりません。
このテクニックの、C++ や、Java や、他の脳死状態の静的な言語への翻訳が、
このキャンプの熱狂的なファンのくだらない課題としては、残っていますが。
caller() 関数で、ちょっとした詮索を加えることが出来ますし、クロージャが
それ自身のパッケージを呼ばない限り、動作を拒否させることも出来ます。
このことは、まちがいなく、プログラミングポリスと、その親類の厳格な人の、
ある種の潔癖な関心事を満足させるでしょう。
もしも、いつ、傲慢- プログラマの三大美徳 - が開始するのかを疑問に
思っていたなら、ここに、傲慢があります。
(もっとまじめにいうなら、傲慢は、ちょっとしたすばらしくよく設計された
コードを書くことから生じる腕前の、プライドです)。
voltar para o topo
(AUTOLOAD: 代理メソッド)
オートロードは、未定義のメソッドの呼び出しをインターセプトする方法です。
オートロードルーチンは、オン・ザ・フライに、新しい関数 - ディスクから
ロードされるか、たぶんevalされているもの - を作ることを選ぶかもしれません。
このオン・ザ・フライに定義する作戦が、
オートローディング(訳註:自動装填?)と呼ばれる理由です。
ですが、これはたった一つの可能なアプローチです。
他のアプローチは、オートロードされたメソッドそれ自身に、直接、要求された
サービスを提供させることです。
この方法を使ったとき、オートロードされたメソッドを``代理''メソッドのように、
考えるかもしれません。
Perlが特定のパッケージの未定義の関数を呼ぼうとし、その関数が
定義されていない場合、同じパッケージにある AUTOLOAD と呼ばれる関数を
探します。
もし、AUTOLOAD があれば、元の関数が持っていたのと同じ引数で、それが
呼ばれます。
その関数の完全な正規の名前(訳註:パッケージ名で修飾された関数名)は、
パッケージのグローバル変数 $AUTOLOAD に蓄えられます。
いったん呼ばれると、関数は、好きなように何かをすることが出来ます。
適当な名前で、新しい関数を定義すること、実際に装飾的な一種の、
gotoを、
それに対して正しく行うこと、コールスタックからそれ自身を消すことを、
含みます(訳註:謎)。
これはオブジェクトと何が関係しますか?
結局、関数について話し続けているのであって、メソッドについてではありません。
たぶん、メソッドは、単に特別な引数と、それが見つけれらる場所について、
装飾的な記法を伴ったただの関数なので、オートローディングをメソッドとしても
使うことが出来ます。
Perl は、@ISA を通した再帰的な捜索に疲れ果てるまで、AUTOLOAD メソッドを
探し始めません。
どんな種類のオブジェクトに対する、未解決のメソッド呼び出しを
トラップするために、UNIVERSAL::AUTOLOAD メソッドを定義する、
プログラマがいることも知られています。
(オートロードされるデータメソッド)
最初に Person クラスと、それから、Employee クラスを見せた、はるか前に
遡ったら、二重化されたコードについて、おそらく、少し疑いを
持ち始めるでしょう。
それぞれのメソッドは、仮想的にまったく同じに見えるハッシュのフィールドに
アクセスしていました。
このことは、偉大なプログラミングの美徳である、短気をくすぐります。
ですが、このときに、不精を勝たせて、なにもしません。
代理メソッドが、これを癒せます。
新しいデータフィールドをが欲しいと思うたびに新しい関数を毎回書く代わりに、
オートロードのメカニズムを使って、(実際に、偽の)メソッドをオン・ザ・フライに
生み出します。
妥当なメンバにアクセスしていることを証明するために、
_permitted(``under-permitted'' と発音します)フィールドでチェックします。
このフィールドは、%fieldと呼ばれるレコードの中の許されたフィールドの、
ファイルスコープのレキシカルな(C のファイル static のような)ハッシュ
リファレンスです。
なぜ、アンダースコアがあるのか?
かつて使った、_CENSUS フィールドと同じ理由のためです:
``内部的に使うためだけ''を意味するマークです。
ここにある、モジュールの初期化コードとクラスのコンストラクタは、
このアプローチを取る場合に見るようなものです:
package Person;
use Carp;
our $AUTOLOAD; # it's a package global
my %fields = (
name => undef,
age => undef,
peers => undef,
);
sub new {
my $class = shift;
my $self = {
_permitted => \%fields,
%fields,
};
bless $self, $class;
return $self;
}
もし、レコードに、デフォルトの値を持たせたいなら、現在の、%field ハッシュに
undef のあるところに、それらを埋めることが出来ます。
オブジェクト自身に、クラスデータのリファレンスを保存させたやりかたに
気づいたでしょうか?
直接に %fields を参照するメソッドを持つか、そのほか、礼儀正しい継承を持たない
代わりに、オブジェクトを通してクラスデータにアクセスすることが
重要であることを思いだしてください。
けれども、本当の魔法は、代理メソッドにあります。
代理メソッドは、Person クラス(また、Person のサブクラス)のオブジェクトに
対する未定義のメソッドの全ての呼び出しを取り扱います。
AUTOLOAD と呼ばれなければなりません。
再び、これは全て大文字になります。
ユーザが直接呼ぶのではなく、Perl 自身によって、暗に呼ばれるからです。
sub AUTOLOAD {
my $self = shift;
my $type = ref($self)
or croak "$self is not an object";
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
unless (exists $self->{_permitted}->{$name} ) {
croak "Can't access `$name' field in class $type";
}
if (@_) {
return $self->{$name} = shift;
} else {
return $self->{$name};
}
}
とっても素敵でしょ? 新しいデータフィールドを加えるときにしなければ
ならないことは、%fields を変更することだけです。
新しい関数を書く必要はありません。
_parmittedフィールドを、完全に、避けることが出来ましたが、どのように
オブジェクトのクラスデータをリファレンスに蓄えるのかを実証したかったのです。
ですので、オブジェクトメソッドから、直接に、クラスデータにアクセスする
必要はありません。
(継承されるオートロードされるデータメソッド)
しかし、継承ではどうなるのでしょう? Employeeクラスを同じように
定義できるでしょうか?
できます。
十分に気をつける限りは。
気をつけ方です:
package Employee;
use Person;
use strict;
our @ISA = qw(Person);
my %fields = (
id => undef,
salary => undef,
);
sub new {
my $class = shift;
my $self = $class->SUPER::new();
my($element);
foreach $element (keys %fields) {
$self->{_permitted}->{$element} = $fields{$element};
}
@{$self}{keys %fields} = values %fields;
return $self;
}
これをしたら、Employee パッケージに、AUTOLOAD 関数を持つ必要はありません。
というのは、継承を通して、Person のバージョンを把握し、全て、
うまく動くからです。
voltar para o topo
(メタクラスなツールたち)
代理メソッドが、データメソッドを関数としてくどくどコーディングするよりも、
より構造体風なクラスを作るのに、便利なアプローチを提供するといっても、
まだ、好ましいものが少し残されています。
第一には、代理メソッドを通してトラップさせるつもりのない、
いんちきの呼び出しを取り扱う必要があることを意味します。
上で詳細を述べたように、継承を扱う時に非常に慎重にならなければならないことも
意味します。
Perlプログラマは、さまざまなクラス生成クラスを作ることで、
これに対応しています。
これらのメタクラスは他のクラスを作るクラスです。
見るべき組は、Class::Struct と、Alias です。
これらと、他の関連するメタクラスは、CPAN のモジュールディレクトリで
見つけられます。
その古いものの一つは、Class::Struct です。
実際、その構文とインターフェースは、perl5 が実際のものに固まるだいぶ前に、
スケッチされました。
Class::Struct は、特定の型のフィールドがあるようなオブジェクトを持つクラスを
``宣言''する方法を提供します。
それが呼ばれる関数は、驚くようなものではなく、struct() です。
構造、またはレコードは、Perl では、基本の型ではないので、
レコード風なオブジェクトを提供するクラスを作るたびに、new() メソッドと、
それに加えて、別個に、レコードの各フィールドのために、
データアクセスメソッドを定義しなければなりません。
このプロセスに、すぐに飽きてしまうでしょう。
Class::Struct::struct 関数はこの退屈なことを緩和します。
これを使う簡単な例です:
use Class::Struct qw(struct);
use Jobbie; # user-defined; see below
struct 'Fred' => {
one => '$',
many => '@',
profession => 'Jobbie', # does not call Jobbie->new()
};
$ob = Fred->new(profession => Jobbie->new());
$ob->one("hmmmm");
$ob->many(0, "here");
$ob->many(1, "you");
$ob->many(2, "go");
print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);
struct の中で、型を宣言でき、それは基本的な Perl の型になるか、
ユーザー定義の型(クラス)になります。
ユーザー型は、そのクラスのnew()メソッドを呼ぶことによって、初期化されます。
Take care that the
Jobbie object is not created automatically by the
Fred class's
new() method, so you should specify a
Jobbie object
when you create an instance of
Fred.
(TBT)
structの生成を使った実際の例があります。
たとえば、gethostbyname() と、gethostbyaddr() を、C 構造体のような振る舞いを
するオブジェクトを返すように、オーバライドしたいとしましょう。
high-falutin' OO gunk は、気にしません。
やりたいことは、これらのオブジェクトが C 感覚で、構造体のような振る舞いを
することです。
use Socket;
use Net::hostent;
$h = gethostbyname("perl.com"); # object return
printf "perl.com's real name is %s, address %s\n",
$h->name, inet_ntoa($h->addr);
Class::Struct モジュールを使ってこれをする方法です。
最重要ポイントは、この呼び出しです:
struct 'Net::hostent' => [ # note bracket
name => '$',
aliases => '@',
addrtype => '$',
'length' => '$',
addr_list => '@',
];
この呼び出しは、上記の名前と型のオブジェクトメソッドを作ります。
new() メソッドさえも作ります。
次の方法でも、オブジェクトを実装できましたが、しませんでした:
struct 'Net::hostent' => { # note brace
name => '$',
aliases => '@',
addrtype => '$',
'length' => '$',
addr_list => '@',
};
こうすると、Class::Struct は、オブジェクト型を、無名配列の代わりに、
無名ハッシュを使います。
配列は、ハッシュより速く、小さいですが、ハッシュは、継承をしたいときに、
よく働きます。
この構造体風なオブジェクには、継承を考えていないので、今回は、
よりよいスピードと、サイズに余裕をもたせることにしました。
実装は次のようになります:
package Net::hostent;
use strict;
BEGIN {
use Exporter ();
our @EXPORT = qw(gethostbyname gethostbyaddr gethost);
our @EXPORT_OK = qw(
$h_name @h_aliases
$h_addrtype $h_length
@h_addr_list $h_addr
);
our %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] );
}
our @EXPORT_OK;
# Class::Struct は、@ISA を使うのを禁止します
sub import { goto &Exporter::import }
use Class::Struct qw(struct);
struct 'Net::hostent' => [
name => '$',
aliases => '@',
addrtype => '$',
'length' => '$',
addr_list => '@',
];
sub addr { shift->addr_list->[0] }
sub populate (@) {
return unless @_;
my $hob = new(); # Class::Struct made this!
$h_name = $hob->[0] = $_[0];
@h_aliases = @{ $hob->[1] } = split ' ', $_[1];
$h_addrtype = $hob->[2] = $_[2];
$h_length = $hob->[3] = $_[3];
$h_addr = $_[4];
@h_addr_list = @{ $hob->[4] } = @_[ (4 .. $#_) ];
return $hob;
}
sub gethostbyname ($) { populate(CORE::gethostbyname(shift)) }
sub gethostbyaddr ($;$) {
my ($addr, $addrtype);
$addr = shift;
require Socket unless @_;
$addrtype = @_ ? shift : Socket::AF_INET();
populate(CORE::gethostbyaddr($addr, $addrtype))
}
sub gethost($) {
if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) {
require Socket;
&gethostbyaddr(Socket::inet_aton(shift));
} else {
&gethostbyname;
}
}
1;
動的なクラスの作成に加えて、コアな関数のオーバーライドや、
import/export の例や、関数のプロトタイピングや、
&whatever を通しての
関数のショートカット呼び出しや、
goto &whatever での
関数の置き換えなどの他のコンセプトの正しい具体例を忍び込ませています。
これらの全ては、大半が、伝統的なモジュールの観点から意味のあるものです。
ですが、お分かりのように、オブジェクトモジュールとして、それらを使うことも
出来ます。
他の、オブジェクトベースのものや、File::stat、Net::hostent、Net::netent、
Net::protoent、Net::servent、Time::gmtime、Time::localtime、User::grent,
User::pwent で、Perl の5.004 リリースのコアな関数の構造体風な
オーバーライドを見ることが出来ます。
これらのモジュールは、コンパイラのプログラムのためにとってある、慣例によって、
全て小文字の最終的な構成要素を持っています。
これらが、コンパイルに影響し、組み込み関数を変更するからです。
これらは、C のプログラマがよく期待する型名を持っています。
(変数としてのデータメンバ)
C++ オブジェクトをよく使うなら、メソッド内からオブジェクトのデータメンバを
単純な変数のように得ることが出来るのになれているでしょう。
Alias モジュールは、これを提供します。
同時に、ちょっとだけよいのは、オブジェクトは呼ぶことが出来るけれど、
クラスの外側のみんなは呼ぶことの出来ない、
プライベートなメソッドのようなところです。
Alias モジュールをつかって Person を作る例です。
これらの魔術的なインスタンス変数を更新すると、自動的にハッシュの値の
フィールドを更新します。
便利でしょ?
package Person;
# this is the same as before...
sub new {
my $class = shift;
my $self = {
NAME => undef,
AGE => undef,
PEERS => [],
};
bless($self, $class);
return $self;
}
use Alias qw(attr);
our ($NAME, $AGE, $PEERS);
sub name {
my $self = attr shift;
if (@_) { $NAME = shift; }
return $NAME;
}
sub age {
my $self = attr shift;
if (@_) { $AGE = shift; }
return $AGE;
}
sub peers {
my $self = attr shift;
if (@_) { @PEERS = @_; }
return @PEERS;
}
sub exclaim {
my $self = attr shift;
return sprintf "Hi, I'm %s, age %d, working with %s",
$NAME, $AGE, join(", ", @PEERS);
}
sub happy_birthday {
my $self = attr shift;
return ++$AGE;
}
our を宣言しなければならないのは、Alias がしているのが、
フィールドと同じ名前のパッケージのグローバル変数をいじれるようにするからです。
要するに、
use strict をしながら、グローバル変数を使うために、
これらを、先に宣言しなければなりません。
これらのパッケージの変数は、ちょうど、それらに、local() を使ったように、
attr() 呼び出しを囲んでいるブロックにローカライズされています。
しかし、このことは、ちょうど、他の
local() のように、まだ、一時的な値で、
グローバル変数をで考えていることを意味します。
Alias と、CLass::Structや、Class::MethodMakerを
混ぜてい使うと良いでしょう。
voltar para o topo
(注意)
(オブジェクト専門用語)
さまざまなオブジェクト指向文献で、たくさんの違った言葉がほんの少ししか
違わない概念を表すのに使われているように思います。
もし、あなたがまだオブジェクト指向プログラマではないなら、
これらの、気まぐれな言葉に悩むことはありません。
ですが、もし、すでに、オブジェクト指向プログラマなら、Perlで、同じ
コンセプトをどのように表すのか知りたいでしょう。
例えば、オブジェクトをクラスの
インスタンスと呼ぶことや、それら
インスタンスのオブジェクトメソッドを
インスタンスメソッド と呼ぶことは、
一般的でしょう。
オブジェクトごとに特有のデータフィールドはよく、
インスタンスデータ とか、
オブジェクト属性と呼ばれ、全てのクラスのメンバに共通のデータフィールドは
クラスデータ や、
クラス属性 や、
静的データメンバ と
よく呼ばれるでしょう。
また、
基底クラス や、
一般クラス や
スーパークラス など全てが、
同じ考えで記述します。
ですので、
派生クラス や
特定クラス や、
サブクラス は、他の関連するものを記述します。
C++ プログラマには、
静的メソッド や
仮想メソッド がありますが、
Perl プログラマには、
クラスメソッド と、
オブジェクトメソッド しかありません。
メソッドが、クラス/オブジェクトメソッドとして使われるかどうかは、
使いかたによるだけです。
うっかりと、オブジェクト(リファレンスを期待するもの)で
クラスメソッド(文字列の引数を期待するもの)を呼ぶことも出来ますし、
その逆も出来ます。
C++ の観点からは、Perl の全てのメソッドは、仮想的です。
ところで、このことは、メソッドが、普通の組み込み関数やユーザ定義関数が
出来るような、引数リストで関数のプロトタイプを、チェックされない理由です。
クラスは、それ自身、オブジェクトの何かですので、Perl のクラスは、
``メタオブジェクトとしてのクラス''(または、
オブジェクト工場 とも
呼ばれます)哲学と、``型定義としてのクラス''(振る舞いを
宣言 し、
メカニズムを
定義しない)考えの両方を表すものとして見られます。
C++ は、後者をサポートしますが、前者は、サポートしません。
voltar para o topo
以下のマニュアルページは、まちがいなく、このマニュアルページに多くの
背景を与えてくれます:
perlmod,
perlref,
perlobj,
perlbot,
perltie,
overload.
perlboot は、よりやさしい、オブジェクト指向プログラミングの
イントロダクションです。
perltooc はクラスデータに関する詳細を提供します。
以下のモジュールは関心をひくでしょう: Class::Accessor,
Class::Class, Class::Contract, Class::Data::Inheritable,
Class::MethodMaker, Tie::SecureHash
voltar para o topo
(著者および著作権)
Copyright (c) 1997, 1998 Tom Christiansen
All rights reserved.
This documentation is free; you can redistribute it and/or modify it
under the same terms as Perl itself.
Irrespective of its distribution, all code examples in this file
are hereby placed into the public domain. You are permitted and
encouraged to use this code in your own programs for fun
or for profit as you see fit. A simple comment in the code giving
credit would be courteous but is not required.
voltar para o topo
(著作権)
(謝辞)
Thanks to
Larry Wall,
Roderick Schertler,
Gurusamy Sarathy,
Dean Roehrich,
Raphael Manfredi,
Brent Halsey,
Greg Bacon,
Brad Appleton,
and many others for their helpful comments.
voltar para o topo