簡単!オブジェクト指向Perl

オブジェクト指向Perlのしくみを解説します。 Perlモジュールの作り方と使用法についても解説します。

[1] パッケージ

名前空間をPerlではパッケージと言います。 デフォールトのパッケージは main パッケージです。 ただしファイルのなかにパッケージ宣言(package ...)の行があると、それ以降に書かれた「グローバル」変数(our 変数)やサブルーチンは、そこで宣言されたパッケージのものになります。

package Foo;

our $name = "I am Foo!";

# $name はパッケージ Foo に入る。
# our は付けなくてもいい...。

パッケージの及ぶ範囲(スコープ)はつぎに又、別のパッケージ宣言が来るまでずっと続きます。 但しパッケージ宣言がブロック {...} のなかで為された場合は、そのブロックが閉じたところでパッケージのスコープが消えます。

package Foo;

our $name = "I am Foo!";
# $name はパッケージ Foo に入る

package Bar;

our $name = "I am Bar!";
# 上の $name はパッケージ Bar に入る
our $name = "I am main!";
# $name は main パッケージに入る

{
    package Foo;

    our $name = "I am Foo!";
# 上の $name は Foo に入る
}

our $age = "100";
# $age は再び main パッケージに入る

例題をあげます。下のスクリプトの実行結果はどうなるでしょう。

&says;

{
    package Foo;

    &says;

    sub says {
	print "&Foo::says is called!\n";
    }
}

&says;
&Foo::says;

sub says {
    print "&main::says is called!\n";
}

答え。上のスクリプトの実行結果は

&main::says is called!
&Foo::says is called!
&main::says is called!
&Foo::says is called!

となります。 このようにパッケージ Foo のサブルーチンを他のパッケージの中から呼び出すためにはサブルーチン名の前にパッケージ名 Foo:: を付ける必要があります。

[2] モジュール

Perlモジュールにはスタンダードなモジュール以外にユーザーが自ら作成するか外部から取り込む等して独自に設けたモジュールがあります。モジュールファイルの名前は *.pm の形をしています。

モジュールにはスクリプトと同じファイル内に書かれ、そのスクリプト内だけで使用されることを目的に作られたものもあります。これを特にインライン・モジュールといったりしますが、サブルーチン等をスクリプト本体とは別のパッケージに書いているだけなので、これはインライン・パッケージとも呼ばれます。

使用可能なモジュールファイルのあるディレクトリは、環境変数@INCを見れば調べられます。 たとえば下のスクリプトで、@INCの中を表示してみましょう。

# @INC の中を表示

foreach my $path (@INC){
    print "$path\n";
}

すると筆者のシステム(ActivePerl on Windows2000)ではこう出ました。

c:/Perl/lib
c:/Perl/site/lib
.

上に列挙されたディレクトリのなかに、システムで利用可能なモジュールファイルが存在しています。

※ 最後のドット . はカレントディレクトリを意味します。

[3] モジュールの作り方

ユーザの独自モジュールはつぎの手順で作ります。

  1. 作業用ディレクトリ $WORK (例として C:/home/perl )に移動する。 
  2. モジュール用のサブディレクトリ(例えば MyModule )に移動する。なければ新しく作る。
  3. テキストエディタを開いてモジュールファイル (例えば Foo.pm) を編集する。

Foo.pm は $WORK から MyModule/Foo.pm (カレントディレクトリからの相対パス)で参照されます。

下は今回使用する Foo.pm の例です。モジュールファイルはこんな形をしています。

# MyModule/Foo.pm

package MyModule::Foo;

sub says {
  print "Hello, I am MyModule::Foo!";
}

1; # Important!!!

このようにモジュールはパッケージ宣言で始まります。

そしてパッケージ名にはモジュールの相対パスの / を :: に換え、末尾の .pm を省いたものを書きます。

つづけて変数やサブルーチンの宣言・定義を書きます。

最後に 1; だけから成る行を書きます。 これは必須です (ただしインラインモジュールにはこの行は不要です)。

[4] モジュールの使い方

つぎに上のモジュールを下のプログラム hello.pl のなかで使ってみましょう。 hello.pl は $WORK にあると仮定します。

  1. $WORK に移動する。
  2. テキストエディタを開いて下のスクリプトを編集する。
# hello.pl

use MyModule::Foo;

&MyModule::Foo::says; # MyModule::Foo はパッケージ名

sub says {
  print "Hello, I am main!";
}

hello.pl の実行結果は下のようになります。

Hello, I am MyModule::Foo!

Hello, I am main! でないことに注意してください。

ところでパッケージ名 MyModule::Foo を一々頭につけてサブルーチン等を呼び出すのは面倒ですね。

これがモジュールをベタに使うことの弱点です。

これを解決するのに、モジュールのエクスポートとインポートという方法がありますが、ここでは解説しません。

いきなりオブジェクト指向Perlの方法へと進むことにします。

[5] オブジェクト指向Perl

以下はオブジェクト指向の基本概念については大体を知っている読者を対象にしています。

オブジェクト指向Perlではクラスはパッケージで実現され、そのパッケージ内のデータ構造がクラスのオブジェクトになります(正確に言うと bless されたデータ構造がオブジェクトになります)。 そしてパッケージ内に書かれたサブルーチンがメソッドになります。

オブジェクト指向Perlの仕組み
オブジェクト指向の基本要素Perlによる実装
クラスパッケージ
オブジェクト(インスタンス)データ構造
メソッドサブルーチン

[6] メソッド

メソッドの書式はこんな感じです。


package Person;

# class method の場合
sub foo {
  my $class = shift;

#---------------------------
# 通常のサブルーチンの内容
#---------------------------

}

# instance method の場合
sub bar {
  my $self = shift;

#---------------------------
# 通常のサブルーチンの内容
#---------------------------

}

このようにサブルーチンの内容が my $variable = shift; の行で始まっているところがメソッドの特徴です。その意味は下の「メソッドの使い方」で明らかになります。

[7] メソッドの使い方

上のクラス(=パッケージあるいはモジュール)を使用するスクリプト本体の例を下に掲げます。

use Person;
# 上のuse 文は Person モジュールが別ファイル
# Person.pm にある場合に必要である。
# inline module の場合は書かない。

# クラスメソッド Person::foo() を呼び出す
Person->foo(); # 引数なし
Person->foo("hello", 20); # 引数つき
my $x = Person->foo(); # $x は returned value を受け取る

# 以下で$p はPersonクラス・オブジェクトへの参照と仮定。
# (Personクラス・オブジェクトそのものの作り方は後述)
# オブジェクトメソッド Person::bar() を呼び出す
$p->bar(); # 引数なし
$p->bar("hello", 30); # 引数つき
$x = $p->bar(); # $x は returned value を受け取る

解説してみましょう。

Person->foo();

これは通常のサブルーチン呼び出しとは違った書式で書かれています。実はここでクラス名 Person がメソッド Person::foo() への第一引数になっているのです。

同じように $p->bar() においてもオブジェクトへの参照 $p がメソッド Person::bar() への第一引数になっています。

これがメソッド定義の本体が my $variable = shift; の行で始まっていることの理由です。 クラスメソッドの場合

my $class = shift;

ですから $class にはクラス名(パッケージ名) Person が入ります。 インスタンスメソッドの場合、

my $self = shift;

ですからオブジェクトへの参照 $p のコピーが $self に入ってきます。

[8] constructors

constructor はオブジェクトを生成するメソッドです。 constructor は本来、クラスメソッドです。 しかしオブジェクトが自身の複製を作成するなど、インスタンスメソッドとしてのconstructorを定義することも可能です。

Perl ではconstructorにつける名前は決まっていません。以下でconstructor の名前を new とするのはそう呼ぶ習慣があるからです(C++ で new が唯一のconstructorである事実に由来すると思われます)。 また Perl では複数の constructor をもつことが可能です。

クラスメソッドとしての constructor は下のような形をしています。

package Person;

sub new {
  my $class = shift;
  my $self = {
    Name => 'none',
    Age => 10,
  };
  return bless $self, $class;
}

解説してみましょう。

Person->new(); が呼ばれると

  1. $class はクラス名 Person を受け取ります。
  2. つぎに Name と Age の二個のフィールドをもつ無名ハッシュ(データ構造)が作られます。
  3. $self が無名ハッシュへの参照になります。
  4. 最後の bless は参照先(無名ハッシュ)にクラス名を付ける役目を果たしています。参照先(無名ハッシュ)はクラス名を付けられて、クラスオブジェクトとなります。bless は $self 自身を返します。
  5. 最後にオブジェクトへの参照が返されます。

[9] constructor の使い方

下のような感じです。

use Person;

my $p = Person->new();

上に解説したように $p がPersonクラスのオブジェクトへの参照となるのですが、言葉の流用で $p 自身をPersonクラスのオブジェクトと言ってしまうこともあります。

なおオブジェクトは上の例にあるような無名ハッシュである必要はありません。 任意のデータ構造のインスタンスへの参照をクラス名で bless しさえすれば、そのデータ構造のインスタンスはクラスのオブジェクトとなるのです。

[10] 引数付きconstructor

引数付きconstructorはデータの初期化に用いられます。上のconstructorをたとえば下のように修正してみます。

sub new {
    my $class = shift;
    my $self = {
        Name => 'none',
        Age => 10,
        @_,
    };
    return bless $self, $class;
}

するとconstructorを

my $p = Person->new( Name => "Saito", Age => 20 );

と呼び出せば、オブジェクトが生成されると同時にデータが上で与えた値に初期化されます。constructor new の「定義」の際に与えたデータ(Name が 'none'で、Ageが10 )はデフォールトの初期値です。

オブジェクトのデータの初期化には次に述べるaccessor methods を用いてこれを行う方法もあります。

[11] accessor methods

オブジェクトのデータを読み書きするメソッドをaccessor methodと呼びます。

accessor methodは各データごとに用意します。たとえば前ページのオブジェクトは

sub new {
  my $class = shift;
  my $self = {
    Name => 'none',
    Age => 10,
  };
  return bless $self, $class;
}

で生成された無名ハッシュで、これはNameとAgeの二つのフィールドをもっています。これらのフィールドを読み書きするメソッドを定義しましょう。

sub name {
  my $self = shift;
  if( @_ ){ $self->{Name} = shift }
  return $self->{Name};
}

sub age {
  my $self = shift;
  if( @_ ){ $self->{Age} = shift }
  return $self->{Age};
}

使い方は

# 読み出しの場合
print $p->name();
my $age = $p->age();

# 書き込みの場合
$p->name("Suzuki");

# 書き込んだ後、読み出す場合
print $p->age(34);

のような感じです。

[12] クラスの継承 (class inheritance)

クラスの継承(class inheritance)には use base qw( BASE ) を使います。BASE には親クラス(base class)となるクラスの名前(パッケージ名)を空白で区切って書き並べます。多重継承をしないならば BASE には唯一の親クラスとなるクラスの名前を書き入れればよいわけです。

下の例では Person class の継承として Student class を定義しています。

# 引数なしコンストラクタの場合

package Person;    # base class

sub new {
    my $class = shift;
    my $self = {
        Name => '',
        Age => 0,
    };
    return bless $self, $class;
}


package Student;    # inherited class
use base qw( Person );

sub new {
    my $class = shift;
    my $self = Person->new;
    $self->{School} = '';
    return bless $self, $class;
}
# 引数付きコンストラクタの場合

package Person;    # base class

sub new {
    my $class = shift;
    my $self = {
        Name => '',
        Age => 0,
        @_,
    };
    return bless $self, $class;
}


package Student;   # inherited class
use base qw( Person );

sub new {
    my $class = shift;
    my $self = Person->new( School => '', @_ );
    return bless $self, $class;
}

使い方は(引数付きの場合)

my $s = Student->new( Name => 'Saito',
                      Age => 20,
                      School => 'Meiji' );

print $s->name('Tanaka'), "\n";
print $s->age, "\n";
print $s->school('Hosei'), "\n";

のような感じです(accessor method が定義されているものとする)。