2013-03-05
オブジェクト指向入門
Perlで、オブジェクト指向プログラミングをするのは難しい、クラスを作成したり、インスタンス変数を定義したりする方法が直感的にわからないと思う人は多いのではないでしょうか。
実は、モジュールの力を借りれば、Perlのオブジェクト指向はとてもわかりやすくなります。今回はObject::Simpleというモジュールを使ってPerlのオブジェクト指向を解説します。
Object::Simpleのインストール
Object::Simpleをインストールします。cpanを使えば簡単にモジュールをインストールできます。
cpan Object::Simple
これで準備が完了です。
クラスの定義
Perlではクラスの定義はpackageを使って行います。最初に-baseフラグを使ってObject::Simpleを継承したクラスを作成します。次の記述は、まずおまじないとして覚えてください。
package MyClass; use Object::Simple -base;
コンストラクタ
このようにするとMyClassというクラスを定義できます。コンストラクタnewを使ってクラスからオブジェクトを生成することができます。(このnewはObject::Simpleから継承されたものです。)
my $obj = MyClass->new;
newメソッドは、ハッシュあるいはハッシュのリファレンスを受け取ることができます。
my $obj = MyClass->new(foo => 1, bar => 2); my $obj = MyClass->new({foo => 1, bar => 2});
これは属性値として設定されます。
アクセッサ
次にアクセッサを定義してみましょう。アクセッサとは属性値(インスタンス変数)にアクセスするためのメソッドのことです。アクセッサを定義すれば、属性値にアクセスすることができます。
アクセッサを生成するにはhas関数を使用します。
has 'foo';
アクセッサを定義すると属性値にアクセスすることができます。
# 値の設定 $obj->foo(1); # 値の取得 my $foo = $obj->foo;
属性値のデフォルトを指定することもできます。
has foo => 1;
属性値が設定されていない場合は最初にアクセッサが呼び出されたときに
デフォルト値が属性値に設定されます。
# デフォルト値の1を取得 my $default = $obj->foo;
もしリファレンスやオブジェクトをデフォルト値として指定したい場合は、サブルーチンのリファレンスの戻り値にする必要があります。
has foo => sub { [] }; has foo => sub { {} }; has foo => sub { MyClass->new };
複数のアクセッサを一度に生成することもできます。
has [qw/foo bar baz/]; has [qw/foo bar baz/] => 0;
継承
継承を行うには-baseを使います。MyClassを継承したSubClassを作ってみましょう。
package MyClass; use Object::Simple -base;
package SubClass; use MyClass -base;
スーパークラスのメソッドの呼び出し
SUPERを使って、スーパークラスのメソッドを呼び出すことができます。
package SubClass; use MyClass -base; sub clear { my $self = shift; $self->SUPER::clear; }
コンストラクタのオーバーライド
サブクラスでコンストラクタをオーバーライドしたい場合があると思います。コンストラクタをオーバーライドするには次のようにします。
package MyClass; use Object::Simple -base; sub new { my $class = shift; my $self = $class->SUPER::new(@_); return $self; }
newはコンストラクタなので必ずオブジェクト自身を返すようにします。
クラスの作成 - 実践編
あら不思議、これだけ覚えればPerlのオブジェクト指向はほぼマスターです。では、実践編です。PointクラスとPoint3Dクラスを作成してみます。
Pointクラス
- Pointは点を表すクラスです。
- xとyというアクセッサを持ちます。
- xとyの値を0にクリアするclearというメソッドを持ちます。
package Point; use Object::Simple -base; has x => 0; has y => 0; sub clear { my $self = shift; $self->x(0); $self->y(0); } 1;
(末尾に「1;」という記述があるのは、モジュールを書くファイルの末尾が真でなければならないというルールがあるためです。)
Pointクラスは以下のように使用することができます。
use Point; my $point = Point->new(x => 3, y => 5); print $point->x; $point->y(9); $point->clear;
Point3Dクラス
- Point3Dは3次元の点を表すクラスです。
- x,y,zというアクセッサを持ちます。
- x,y,zの値を0にクリアするclearというメソッドを持ちます。
Point3DはPointを継承して作成します。clearメソッドはxとyとzの値をクリアするためにオーバーライドされています。
package Point3D; use Point -base; has z => 0; sub clear { my $self = shift; $self->SUPER::clear; $self->z(0); } 1;
Point3Dクラスは以下のように利用することができます。
use Point3D; my $point = Point->new(x => 3, y => 5, z => 8); print $point->z; $point->z(9); $point->clear;
実行できるサンプル
実行できるサンプルを書いておきますね。
lib/Point.pm
libというディレクトを作成して、その中にPoint.pmというファイルを作成してください。
package Point; use Object::Simple -base; has x => 0; has y => 0; sub clear { my $self = shift; $self->x(0); $self->y(0); } 1;
lib/Point3D.pm
libというディレクトの中にPoint3D.pmというファイルを作成してください。
package Point3D; use Point -base; has z => 0; sub clear { my $self = shift; $self->SUPER::clear; $self->z(0); } 1;
test.pl
クラスを読み込んで利用するサンプルです。libモジュールを使うことで、モジュールの検索パスを追加できます。
use strict; use warnings; use lib 'lib'; use Point3D; my $point = Point3D->new(x => 3, y => 5, z => 8); # 8と表示される print $point->z . "\n"; $point->z(9); # 9と表示される print $point->z . "\n"; $point->clear; # 0と表示される print $point->z . "\n";
スクリプトの実行
ディレクトリ構成は以下のようにします。
test.pl
lib - Point.pm
- Point3D.pm
以下のように実行してください。
perl test.pl
オブジェクト指向プログラミングの概念
継承
Object::Simpleをよく理解するために、オブジェクト指向の概念を解説したいと思います。
オブジェクト指向の一つ目の概念は「継承」です。「継承」とは「クラスQがクラスPを継承していたら、クラスQはクラスPのすべてのメソッドを呼び出すことができる」ということを意味します。
+---+ | P | スーパークラス +---+ method1とmethod2を定義 | +---+ | Q | サブクラス +---+ method3を定義
クラスQはクラスPを継承しているので、クラスQはクラスQのメソッドに加えて、クラスPのすべてのメソッドを呼び出すことができます。言い換えれば、クラスQはmethod1, method2とmethod3を呼び出すことができます。
package P; use Object::Simple -base; sub method1 { ... } sub method2 { ... }
package Q; use P -base; sub method3 { ... }
Perlはオブジェクト指向プログラミングを助ける便利な関数とメソッドを持っています。オブジェクトがどのクラスに属しているかを知るには、ref関数を使用します。
my $class = ref $obj;
オブジェクトが特定のクラスを継承しているかどうかを調べるには、isaメソッドを使用します。
$obj->isa('MyClass');
オブジェクト(あるいはクラス)が特定のメソッドを呼び出すことができるかどうかを知るには、canメソッドを使用します。
MyClass->can('method1'); $obj->can('method1');
カプセル化
オブジェクト指向プログラミングのふたつ目の概念はカプセル化です。「カプセル化」は「内部的にデータに直接アクセスしてはいけない」ということを意味します。ドキュメントに記述された公開されたメソッドを使用しなければなりません。このルールを守ることによって、すべてのことがシンプルになります。
このルールを守るためには値を取得や設定を行うためのアクセッサを生成する必要があります。
my $value = $obj->foo; $obj->foo(1);
直接データにアクセスするのは良くない習慣です。
# 良くない my $value = $obj->{foo}; $obj->{foo} = 1;
ポリモーフィズム
オブジェクト指向プログラミングの三つ目の概念は「ポリモーフィズム」です。
「ポリモーフィズム」は、「オーバーロード」と「オーバーライド」のふたつの概念に
分割されます。
Perlプログラマはオーバーロードを気にする必要はありません。Perlは動的な言語なので、サブルーチンはどのような値でも受け取ることができます。オーバーロードはC++やJavaなどの静的な型を持つ言語にとって必要です。
「オーバーライド」は「サブクラスにおいて、基底クラスのメソッドを変更することができる」ということを意味します。
package P; use Object::Simple -base; sub method1 { return 1 }
package Q; use P -base; sub method1 { return 2 }
クラスPのmethod1は1という値を返却します。クラスQのmethod1は2という値を返却します。つまり、クラスQにおいて、method1はオーバーライドされたということです。
# 1を返す my $obj_p = P->new; $obj_p->method1; # 2を返す my $obj_q = Q->new; $obj_q->method1; # Return value is 2
もし基底クラスのメソッドをサブクラスから呼び出したい場合はSUPER擬似クラスを使用します。
package Q; use Object::Simple -base; sub method1 { my $self = shift; # SUPER擬似クラスを使ってPのmethod1を呼び出す my $value = $self->SUPER::method1(@_); return 2 + $value; }
newはオーバーライドすることができます。たとえば、オブジェクトの初期化処理を行いたい場合は、newをオーバーライドします。
sub new { my $self = shift->SUPER::new(@_); # 初期化処理 return $self; }
newの引数を変更したい場合もnewをオーバーライドすることで解決できます。
sub new { my $self = shift; $self->SUPER::new(x => $_[0], y => $_[1]); return $self; }
これらの三つの概念「継承」「カプセル化」「ポリモーフィズム」を理解するならば、十分強力なオブジェクト指向プログラムができ、ソースコードは他の言語のユーザから見ても読みやすいものになるでしょう。
パッケージブロック構文
Perl 5.14では、パッケージブロック構文というものが追加されて、次のように記述することができ、ファイルの末尾に「1;」を書かなくてもよくなりました。Perl5.14以上のPerlを使っている方は、利用してみるのもよいと思います。
package Point { use Object::Simple -base; has x => 0; has y => 0; sub clear { my $self = shift; $self->x(0); $self->y(0); } }
まとめ
Perlは柔軟な言語です。Perlという言語の中で、完全なオブジェクト指向を行うことができます。Perlのオブジェクト指向は後付けの実装と揶揄されることがありますが、機能としてはオブジェクト指向として、完全なものをそなえています。
Perlは名前空間を持ち、オブジェクト指向が可能なので、大規模開発にも十分耐えることができます。規模が大きくなってきたら、オブジェクト指向でプログラミングするとよいと思います。


