Perl 5のOOP学習ノート
Perlランキングが下がり続ける中でPerlを学ぶのは、少し賢明ではないようだ.でも、仕事が必要だから、勉強しなければなりませんね.それに、Perlは今でもテスト分野で非常に多く使われています.Phythonもテスト分野で活躍し始めましたが、Phythonの文法はあまり好きではありません.
Perlの基本文法を勉強した後、PerlのOOPを勉強して、少し心得があります.Perlの各バージョンの間にOOPの違いがあるかどうかはわかりませんが、私は勉強しているPerl 5なので、タイトルにバージョン番号も書きました.PHP 4とPHP 5のOOP部分に少なからぬ差があることが分かったので、この心配がありました.
PerlのOOPを学ぶには、最も重要な2つのことがpackageとblessです.この二つのものをはっきりさせるだけで半分になる.
PerlのpackageはJavaと似ているような気がします.JavaのパッケージはCLASSSPATHのディレクトリをルートとし、ディレクトリごとに階層化されたパッケージ名を定義および検索します.Perlも同様に,@INC配列のディレクトリをルートとし,ディレクトリ別に階層化パケット名を検索する.ただし、Perlのpackage定義はディレクトリ構造に対応する必要はないようです.具体的にどのようなルールなのかは研究していません.ディレクトリ構造によってpackageを定義するのは良い習慣だからです.
Javaに比べて、Perlのパッケージはもうちょっと面白いです.Javaの各レイヤpackageはディレクトリに対応し、最後にclassファイルはクラス名に対応します.Perlは簡略化され、packageは直接ディレクトリとファイル名を引用した.たとえば
Java中、name.jamesfancy.MyClass、対応は/name/jamesfancy/MyClassです.class、ソースコードは2つに分けて書きます
Perlでは、name::jamesfancy::MyClass、対応は/name/jamesfancy/MyClass.pm、ソースコードにはpackageが1つだけ説明されています
パッケージの内容、すなわち変数とサブルーチンの違いについては、後で話します.
blessは、クラスを参照タイプ変数にバインドするための関数です.Perlがなぜこの単語を使うのか不思議ですが、大丈夫です.私たちはそれをイメージすることができます.ゲームの牧師が祝福スキルを通じて誰かにBUFFを加えるように、blessはクラスを引用タイプの変数にバインドし、それからこの変数は祝福され、このクラスの変数とサブプログラムを持っています.
blessの使い方は通常、bless($参照変数、クラス名);
参照変数は任意の参照タイプの変数であるように見えますが、Scalar、Array、Hashの参照を試してみましたが、成功しました.bless以外では、この参照変数をオブジェクトと呼ぶことができます.もちろん、オブジェクトの参照です.
このオブジェクトはクラスの変数とサブプログラムを持っていますが、私たちはその持つクラスの変数とサブプログラムを静的と見なすべきです.言い換えれば、クラスのメンバーです.この点,サブルーチンの処理は比較的特殊であるが,少なくともクラスの変数,すなわちパケット変数はオブジェクトに属さない.したがって、すべてのオブジェクトのデータは、オブジェクト参照の元のデータに保存されます.オブジェクトデータをキー値ペアで保存することに慣れている以上,通常blessの参照変数はHashの参照である.
抽象的ですか?例を挙げる.OOPのメンバー関数がまだ理解されていない場合は、次の例の各クラスのtest関数の最初の文以降の内容だけを見てください.
上記の例から、3種類の参照をそれぞれオブジェクトに変換していることがわかります.クラスを1つではなく3つに書くのは,主にTestに異なるタイプのデータを出力するためである.
メンバー関数はpackageで定義されたサブルーチンです.メンバー関数には静的と非静的の区別はありませんが、クラスメンバー関数として呼び出すことも、オブジェクトメンバー関数として呼び出すこともできますが、オブジェクトメンバー関数として呼び出すと、Perlがオブジェクト参照にこっそり入ってくるので、静的関数と見なしたほうがいいです.これは、通常のメンバー関数の最初の文が
もちろん、ここの$thisはキーワードではなくローカル変数であり、別の名前で置き換えることもできます.例えば、多くの人が$selfや$meなどを使うのが好きです.
もし、1つのメンバー関数に対してクラスとオブジェクトで呼び出されるとしたら、何が違いますか?もう1つの例を見てみましょう.
結果から,いずれのメソッド呼び出しにおいても,最初のパラメータはPerlがこっそり伝達していることがわかる.クラス呼び出しの場合、最初のパラメータはクラスです.オブジェクト呼び出しの場合、最初のパラメータはオブジェクトです.したがって、ref($this)の結果とクラス名を比較するだけで、どの呼び出しであるかがわかります.したがって、許容誤差の良いメンバー関数は、最初に入力された最初のパラメータ、例えば
ここでもう一つ疑問があります.packageで定義されているサブルーチンがメンバー関数である以上、クラスではないpackageとクラスであるpackageの違いは何ですか.それらは構造的に少しも違いがなく、唯一の違いは処理中です.サブプログラムを呼び出すとき、Perlはクラスやオブジェクトをパラメータリストの一番前に押し込むことはありませんが、メンバー関数を呼び出すときになるので、違いはあなたの呼び出し方法によって区別されます.
オブジェクトメンバーを呼び出すのはいいですが、$obj->foo()でいいですが、クラスメンバーを呼び出すときに、呼び出されたクラスメンバーなのかパッケージのサブプログラムなのか、どうしてわかりますか.それは「->」か「:::」かで呼び出されます.次の例は理解に役立ちます.
「::」で呼び出されたサブルーチンは、Perlによって参照クラスのパラメータに埋め込まれていないことは明らかです.
PerlのOOPは専用のコンストラクション関数を指定していないので、どのサブルーチンもコンストラクション関数として使用することができます.もちろん、重要なのはその内容です.シナリオは通常自分一人に書くものではないので、皆さんの習慣に従って、コンストラクション関数をnewと名付けましょう.多くのOOP言語の習慣に従って、new関数は通常、オブジェクトまたはその参照、ポインタを返します.したがってPerlでは,このnew関数はオブジェクト参照を返すので,当然bless動作をnew関数に含めるのがよい習慣である.簡単なnew関数はこう見えます
このnew関数ではHash参照,bless参照が生成され,返される.なぜここにreturn文が見えないのか疑問に思ったら、サブルーチンの戻り値に関する資料を見て、bless関数の説明を調べることをお勧めします.完全なプログラムを見てnew関数の使い方を理解してみましょう.
上のnew MyClass()の効果はMyClass->new()の効果と同じです.ここでnewはキーワードではなく、関数名です.同じように、fooメンバー関数があればfoo MyClass(args)でもいいですが、実際にはMyClass::foo(MyClass,args);
ところで、オブジェクトデータを初期化する必要がある場合はどうすればいいのでしょうか.前述したように、オブジェクトデータは参照されたデータ自体に保存されているので、Hash参照blessをオブジェクトにするのが一般的です.このようにnewを呼び出すのをよく見ます
または
2つの呼び出し方式の違いは、前者がHashエンティティに伝達され、後者がHash参照に伝達されるため、new関数での処理が異なることである.この2つの状況を互換化するために、new関数は通常、次のプログラムのように書きます.
一般的にHash参照がblessによってオブジェクト化されている以上、この場合だけを言います.
Hash参照である以上、データにアクセスする最も簡単な方法はHash参照にアクセスすることと同じです.たとえば
カッコを少なめに書く場合は、setter/getterを定義することで解決できます.getterとsetterはパラメータの有無によって区別できるので、次のname関数のような関数にマージすることが可能になります.
setter/getterを使用すると、確かにプログラムが簡潔に見えます.しかし、オブジェクトのデータごとにgetter/setterを書くのは疲れるので、AUTOLOAD関数を持ち出して、次のプログラムを見てみましょう.
このように呼び出すのはずっと便利ではないでしょうか.でもAUTOLOADは書くのが大変です.データ・オブジェクトが1つしか必要ない場合は、ネット上にHash::AsObjectのクラスが使いやすく、使い方は上記の最後の例とあまり違いません.
私は確かにこの方面を継承することについてあまり研究していません.単純な継承はuse base文でベースクラスを導入することにすぎません.例えば
東南大学出版社、O'Reillyの『Perl(影印版)』、brian d foy著Perl version 5.10.0 documentation、http://perldoc.perl.org/Hash::AsObjectソース、ソースhttp://search.cpan.org/~nkuitse/Hash-AsObject-0.11/lib/Hash/AsObject.pm
Perlの基本文法を勉強した後、PerlのOOPを勉強して、少し心得があります.Perlの各バージョンの間にOOPの違いがあるかどうかはわかりませんが、私は勉強しているPerl 5なので、タイトルにバージョン番号も書きました.PHP 4とPHP 5のOOP部分に少なからぬ差があることが分かったので、この心配がありました.
PerlのOOPを学ぶには、最も重要な2つのことがpackageとblessです.この二つのものをはっきりさせるだけで半分になる.
Perlのパッケージ
PerlのpackageはJavaと似ているような気がします.JavaのパッケージはCLASSSPATHのディレクトリをルートとし、ディレクトリごとに階層化されたパッケージ名を定義および検索します.Perlも同様に,@INC配列のディレクトリをルートとし,ディレクトリ別に階層化パケット名を検索する.ただし、Perlのpackage定義はディレクトリ構造に対応する必要はないようです.具体的にどのようなルールなのかは研究していません.ディレクトリ構造によってpackageを定義するのは良い習慣だからです.
Javaに比べて、Perlのパッケージはもうちょっと面白いです.Javaの各レイヤpackageはディレクトリに対応し、最後にclassファイルはクラス名に対応します.Perlは簡略化され、packageは直接ディレクトリとファイル名を引用した.たとえば
Java中、name.jamesfancy.MyClass、対応は/name/jamesfancy/MyClassです.class、ソースコードは2つに分けて書きます
- package name.jamesfancy;
- class MyClass {....}
Perlでは、name::jamesfancy::MyClass、対応は/name/jamesfancy/MyClass.pm、ソースコードにはpackageが1つだけ説明されています
- package name::jamesfancy::MyClass;
パッケージの内容、すなわち変数とサブルーチンの違いについては、後で話します.
bless関数
blessは、クラスを参照タイプ変数にバインドするための関数です.Perlがなぜこの単語を使うのか不思議ですが、大丈夫です.私たちはそれをイメージすることができます.ゲームの牧師が祝福スキルを通じて誰かにBUFFを加えるように、blessはクラスを引用タイプの変数にバインドし、それからこの変数は祝福され、このクラスの変数とサブプログラムを持っています.
blessの使い方は通常、bless($参照変数、クラス名);
参照変数は任意の参照タイプの変数であるように見えますが、Scalar、Array、Hashの参照を試してみましたが、成功しました.bless以外では、この参照変数をオブジェクトと呼ぶことができます.もちろん、オブジェクトの参照です.
このオブジェクトはクラスの変数とサブプログラムを持っていますが、私たちはその持つクラスの変数とサブプログラムを静的と見なすべきです.言い換えれば、クラスのメンバーです.この点,サブルーチンの処理は比較的特殊であるが,少なくともクラスの変数,すなわちパケット変数はオブジェクトに属さない.したがって、すべてのオブジェクトのデータは、オブジェクト参照の元のデータに保存されます.オブジェクトデータをキー値ペアで保存することに慣れている以上,通常blessの参照変数はHashの参照である.
抽象的ですか?例を挙げる.OOPのメンバー関数がまだ理解されていない場合は、次の例の各クラスのtest関数の最初の文以降の内容だけを見てください.
- # test.pl
- package TestScalar;
- sub test {
- my $this = shift();
- print("/nIn TestScalar::test()/n");
- print("Scalar:/n ${$this}/n");
- }
-
- package TestArray;
- sub test {
- my $this = shift();
- print("/nIn TestArray::test()/n");
- print("Array:/n");
- foreach my $item (@{$this}) {
- print(" $item/n");
- }
- }
-
- package TestHash;
- sub test {
- my $this = shift();
- print("/nIn TestHash::test()/n");
- print("Hash:/n");
- while (my ($key, $value) = each %{$this}) {
- printf(" %-4s = %s/n", $key, $value);
- }
- }
-
- package main;
-
- my $name = "James Fancy";
- my $objScalar = /$name;
- my $objArray = ['James', 'Fancy', 'Jenny'];
- my $objHash = {'name' => 'James', 'age' => 30};
-
- bless($objScalar, 'TestScalar');
- bless($objArray, 'TestArray');
- bless($objHash, 'TestHash');
-
- $objScalar->test();
- $objArray->test();
- $objHash->test();
-
- __END__
-
- In TestScalar::test()
- Scalar:
- James Fancy
-
- In TestArray::test()
- Array:
- James
- Fancy
- Jenny
-
- In TestHash::test()
- Hash:
- name = James
- age = 30
上記の例から、3種類の参照をそれぞれオブジェクトに変換していることがわかります.クラスを1つではなく3つに書くのは,主にTestに異なるタイプのデータを出力するためである.
クラスとオブジェクトのメンバー関数
メンバー関数はpackageで定義されたサブルーチンです.メンバー関数には静的と非静的の区別はありませんが、クラスメンバー関数として呼び出すことも、オブジェクトメンバー関数として呼び出すこともできますが、オブジェクトメンバー関数として呼び出すと、Perlがオブジェクト参照にこっそり入ってくるので、静的関数と見なしたほうがいいです.これは、通常のメンバー関数の最初の文が
- my $this = shift();
もちろん、ここの$thisはキーワードではなくローカル変数であり、別の名前で置き換えることもできます.例えば、多くの人が$selfや$meなどを使うのが好きです.
もし、1つのメンバー関数に対してクラスとオブジェクトで呼び出されるとしたら、何が違いますか?もう1つの例を見てみましょう.
- # test.pl
- package MyClass;
-
- sub test {
- my ($this, @args) = @_;
- print('-' x 40, "/n");
- print("/$this is [$this], Ref of /$this is [", ref($this), "]/n");
- print("Args: [@args]/n");
- }
-
- package main;
-
- $obj = {};
- bless($obj, 'MyClass');
-
- MyClass->test("MyClass->test(...)");
- $obj->test("/$obj->test(...)");
-
- __END__
- ----------------------------------------
- $this is [MyClass], Ref of $this is []
- Args: [MyClass->test(...)]
- ----------------------------------------
- $this is [MyClass=HASH(0x178a44)], Ref of $this is [MyClass]
- Args: [$obj->test(...)]
結果から,いずれのメソッド呼び出しにおいても,最初のパラメータはPerlがこっそり伝達していることがわかる.クラス呼び出しの場合、最初のパラメータはクラスです.オブジェクト呼び出しの場合、最初のパラメータはオブジェクトです.したがって、ref($this)の結果とクラス名を比較するだけで、どの呼び出しであるかがわかります.したがって、許容誤差の良いメンバー関数は、最初に入力された最初のパラメータ、例えば
- sub foo {
- my $this = shift();
- return unless ($this ne 'MyClass');
- #
- }
ここでもう一つ疑問があります.packageで定義されているサブルーチンがメンバー関数である以上、クラスではないpackageとクラスであるpackageの違いは何ですか.それらは構造的に少しも違いがなく、唯一の違いは処理中です.サブプログラムを呼び出すとき、Perlはクラスやオブジェクトをパラメータリストの一番前に押し込むことはありませんが、メンバー関数を呼び出すときになるので、違いはあなたの呼び出し方法によって区別されます.
オブジェクトメンバーを呼び出すのはいいですが、$obj->foo()でいいですが、クラスメンバーを呼び出すときに、呼び出されたクラスメンバーなのかパッケージのサブプログラムなのか、どうしてわかりますか.それは「->」か「:::」かで呼び出されます.次の例は理解に役立ちます.
- # test.pl
- package MyClass;
- use Data::Dumper;
- sub test {
- print('-' x 40, "/n");
- print(Dumper(@_));
- }
-
- package main;
-
- MyClass->test("MyClass->test(...)");
- MyClass::test("MyClass::test(...)");
-
- __END__
- ----------------------------------------
- $VAR1 = 'MyClass';
- $VAR2 = 'MyClass->test(...)';
- ----------------------------------------
- $VAR1 = 'MyClass::test(...)';
「::」で呼び出されたサブルーチンは、Perlによって参照クラスのパラメータに埋め込まれていないことは明らかです.
コンストラクタ
PerlのOOPは専用のコンストラクション関数を指定していないので、どのサブルーチンもコンストラクション関数として使用することができます.もちろん、重要なのはその内容です.シナリオは通常自分一人に書くものではないので、皆さんの習慣に従って、コンストラクション関数をnewと名付けましょう.多くのOOP言語の習慣に従って、new関数は通常、オブジェクトまたはその参照、ポインタを返します.したがってPerlでは,このnew関数はオブジェクト参照を返すので,当然bless動作をnew関数に含めるのがよい習慣である.簡単なnew関数はこう見えます
- sub new {
- my $this = {};
- bless($this);
- }
このnew関数ではHash参照,bless参照が生成され,返される.なぜここにreturn文が見えないのか疑問に思ったら、サブルーチンの戻り値に関する資料を見て、bless関数の説明を調べることをお勧めします.完全なプログラムを見てnew関数の使い方を理解してみましょう.
- # test.pl
- package MyClass;
-
- sub new {
- my $this = {};
- bless($this);
- }
-
- package main;
-
- my $obj1 = MyClass::new();
- my $obj2 = MyClass->new();
- my $obj3 = new MyClass();
-
- print(join("/n", ref($obj1), ref($obj2), ref($obj3)));
-
- __END__
- MyClass
- MyClass
- MyClass
上のnew MyClass()の効果はMyClass->new()の効果と同じです.ここでnewはキーワードではなく、関数名です.同じように、fooメンバー関数があればfoo MyClass(args)でもいいですが、実際にはMyClass::foo(MyClass,args);
ところで、オブジェクトデータを初期化する必要がある場合はどうすればいいのでしょうか.前述したように、オブジェクトデータは参照されたデータ自体に保存されているので、Hash参照blessをオブジェクトにするのが一般的です.このようにnewを呼び出すのをよく見ます
- my $obj = MyClass->new('key1' => 'value1', 'key2' => 'value2');
または
- my $obj = MyClass->new({'key1' => 'value1', 'key2' => 'value2'});
2つの呼び出し方式の違いは、前者がHashエンティティに伝達され、後者がHash参照に伝達されるため、new関数での処理が異なることである.この2つの状況を互換化するために、new関数は通常、次のプログラムのように書きます.
- # test.pl
- package MyClass;
-
- sub new {
- my $class = shift();
- my $this = ref(@_[0]) ? @_[0] : {@_};
- bless($this);
- }
-
- package main;
- use Data::Dumper;
-
- my $obj1 = MyClass->new('name' => 'James Fancy', 'age' => 30);
- my $obj2 = MyClass->new({'name' => 'James Fancy', 'age' => 30});
-
- print(Dumper($obj1));
- print(Dumper($obj2));
-
- __END__
- $VAR1 = bless( {
- 'name' => 'James Fancy',
- 'age' => 30
- }, 'MyClass' );
- $VAR1 = bless( {
- 'name' => 'James Fancy',
- 'age' => 30
- }, 'MyClass' );
オブジェクトデータへのアクセス
一般的にHash参照がblessによってオブジェクト化されている以上、この場合だけを言います.
Hash参照である以上、データにアクセスする最も簡単な方法はHash参照にアクセスすることと同じです.たとえば
- $obj->{'name'} = "You Name";
- my $name = $obj->{'name'};
カッコを少なめに書く場合は、setter/getterを定義することで解決できます.getterとsetterはパラメータの有無によって区別できるので、次のname関数のような関数にマージすることが可能になります.
- # test.pl
- package MyClass;
-
- sub new {
- my $class = shift();
- my $this = ref(@_[0]) ? @_[0] : {@_};
- bless($this);
- }
-
- sub name {
- my $this = shift();
- if (@_[0]) {
- $this->{'name'} = @_[0];
- }
- return $this->{'name'};
- }
-
- package main;
-
- my $obj = MyClass->new('name' => 'James Fancy');
- print($obj->name, "/n");
- print($obj->name("New Name"), "/n");
-
- __END__
- James Fancy
- New Name
setter/getterを使用すると、確かにプログラムが簡潔に見えます.しかし、オブジェクトのデータごとにgetter/setterを書くのは疲れるので、AUTOLOAD関数を持ち出して、次のプログラムを見てみましょう.
- package MyClass;
-
- sub new {
- my $class = shift();
- my $this = ref(@_[0]) ? @_[0] : {@_};
- bless($this, $class);
- }
-
- sub AUTOLOAD {
- my $this = $_[0];
- if (!ref($this)) {
- return;
- }
-
- my $name = $AUTOLOAD;
-
- if (defined($name)) {
- $name =~ s/.*:://;
- } else {
- return;
- }
-
- my $class = ref($this);
- if (defined($this->{$name}) || @_) {
- no strict 'refs';
- *{"${class}::$name"} = sub {
- my $this = shift();
- $this->{$name} = shift() if (@_);
-
- # make a property in hash reference type to HashObject object.
- if (ref($this->{$name}) eq 'HASH') {
- bless($this->{$name}, $class);
- }
-
- return $this->{$name};
- };
-
- goto &$name;
- }
- }
-
- package main;
-
- my $obj = MyClass->new('name' => 'James Fancy');
- $obj->more1({'key', 'value of more1->key'});
- print($obj->name, "/n");
- print($obj->more1->key, "/n");
- print($obj->more2({})->key("value of more2->key"), "/n");
-
- __END__
- James Fancy
- value of more1->key
- value of more2->key
このように呼び出すのはずっと便利ではないでしょうか.でもAUTOLOADは書くのが大変です.データ・オブジェクトが1つしか必要ない場合は、ネット上にHash::AsObjectのクラスが使いやすく、使い方は上記の最後の例とあまり違いません.
継承
私は確かにこの方面を継承することについてあまり研究していません.単純な継承はuse base文でベースクラスを導入することにすぎません.例えば
- package Parent;
-
- sub test1 {
- print("Parnet::test1/n");
- }
-
- sub test {
- print("Parent::test/n");
- }
-
- package Sub;
- use base Parent;
-
- sub test {
- print("Sub::test/n");
- }
-
- sub test2 {
- $_[0]->Parent::test();
- }
-
- package main;
-
- my $obj = bless({}, *Sub);
- $obj->test();
- $obj->test1();
- $obj->test2();
-
- __END__
- Sub::test
- Parnet::test1
- Parent::test
参考資料
東南大学出版社、O'Reillyの『Perl(影印版)』、brian d foy著Perl version 5.10.0 documentation、http://perldoc.perl.org/Hash::AsObjectソース、ソースhttp://search.cpan.org/~nkuitse/Hash-AsObject-0.11/lib/Hash/AsObject.pm