4部 オブジェクト指向

オブジェクト指向の基礎

Perlはオブジェクト指向をサポートしています。ところで、そのオブジェクト指向プログラミングというのは、本来はプログラミングを楽にしてくれるものですが、難解な用語が多く、理解するだけでヘトヘトになっちゃうような代物です。そこで、この章では、小難しい定義や解説はなるべく回避して、なるべく簡単にオブジェクト指向を利用できるように解説していきます。

オブジェクト指向を知ろう!

オブジェクト指向のオブジェクトとは、そのものずばり「物」の概念をモデル化したものです。オブジェクトは物と同様に、様々な性質を持つことができ、また、その性質によって分類することができます。

オブジェクトとは?

オブジェクト指向のオブジェクトは、現実世界での"物"の状態と振る舞いを再現します。たとえば、パソコンでいえばメモリ、ハードディスクなどの状態に、起動・終了といった振る舞いがあります。オブジェクト指向では、状態を変数で、振る舞いをサブルーチンで再現します。オブジェクト指向でのサブルーチンは、メソッドと呼ばれ、サブルーチンにはない特性があります。

クラスとは?

メーカーは、ひとつの設計図があれば、ひとつの製品を大量生産することができます。それと同じように、オブジェクト指向でも、設計図を再現するためにクラスというものが用意されています。
 今まではパッケージによって変数やサブルーチンの名前空間を管理していましたが、オブジェクト指向ではクラスによって管理します。クラスはオブジェクトを管理する役割を担当し、再利用するための手段を提供しています。

メソッドとは?

オブジェクト指向でのサブルーチンはメソッドと呼ばれ、通常のサブルーチンにはない特性があります。それは、メソッドに渡す第1引数が必ずオブジェクトへのリファレンスであることが決められていることです。

カプセル化とは?

大量のプログラミングを行うとき、オブジェクト指向はとても魅力的な手法です。大量のプログラミングが必要になるときに、最初に考えることは、資源の安全な再利用です。何度も同じような処理をする際は、何度も同じソースを書いたりせずに、一度書いたソースコードを利用して処理したいものです。また、後でソースコードの一部を変更した際に、すべてのソースを書き換えるようなことは避けたいですね。
オブジェクト指向は、データのカプセル化によって資源の安全な再利用方を提供しています。データのカプセル化とは、オブジェクト間の通信を提供し、内部の情報を隠蔽するということです。オブジェクト同士は互いと通信して利用者に機能を提供しますが、お互いに独立しています。ですから、オブジェクトを新しく追加したり、変更を加える際にすべてのオブジェクトへの影響を最小限に抑えることができます。また、ほかの利用者のために、すべてのソースコードを説明する必要はありません。その使い方さえドキュメント化しておけば、誰でも利用することができます。

オブジェクト指向用語

オブジェクト
オブジェクトは、ハッシュや配列などのデータ型が、パッケージに blessされクラスへのリファレンスです。
クラス
メソッドを提供するパッケージ
メソッド
オブジェクトへのリファレンスを最初の引数として期待するサブルーチン

クラスを作ってみよう!

クラスはパッケージの別名みたいなものです。利用可能なクラスを作成するには、package文によってクラスを宣言し、コンストラクタと呼ばれるメソッドを作成します。

クラスの宣言

クラスはパッケージですから、package 文を使ってクラスを宣言します。

package クラス名;

たとえば、クラス名 Class を作成するには次のように宣言します。

package Class;

クラスを宣言したら、あとはパッケージと同様にメソッドを定義していきます。ただし、クラスはパッケージと違って、コンストラクタと呼ばれる特殊なメソッドを定義する必要があります。

コンストラクタの作成

コンストラクタは、新しいオブジェクトを作成し、そのリファレンスを返すメソッドです。クラス作成の順番は次のようになります。

  1. newという名前でメソッドを定義する
  2. 第1引数のクラス名を受け取る
  3. bless関数でオブジェクトのリファレンスを返す

コンストラクタは newという名前で宣言します。第1引数としてクラス名が渡される決まりなので、それを受け取りましょう。

package Class;
sub new {
	my $class = shift;
	....
}

次に、オブジェクトを用意します。ここでは、無名ハッシュのリファレンスを作成していますが、ハッシュ以外にもスカラや配列を返しても問題ありません。

# 無名ハッシュのリファレンスを作成
my $self = {};

ここでオブジェクト属性をセットしておくこともできます。

my $self = { x=>10, y=>20 };

あとはbless関数に作成したオブジェクトとクラス名を渡して終わりです。

# bless したオブジェクトリファレンスを返す
return bless $self, $class;
簡単なクラスの作成
# クラスのパッケージ名を宣言
package Class;
#########################################
解説:クラスメソッドコンストラクタ
#########################################
sub new{
	# 暗黙のうちに引き渡されるクラス名を受け取る
	my $class = shift;
	# 無名ハッシュのリファレンスを作成
	my $self = {};
	# bless したオブジェクトリファレンスを返す
	return bless $self, $class;
}

bless関数とは?

bless関数の引数は、リファレンスとパッケージ名になります。第1引数で与えられたリファレンスが指すオブジェクトは、第2引数で指定したクラスに所属します。
第2引数のパッケージ名はオプションで、省略すると現在のパッケージ名を引数とします。

my $self = {};
bless $self, $class;

blessした変数はクラスのオブジェクトに属することになるので、普通の変数と区別するためにインスタンスと呼ばれます。ここでは単純に、「blessの第1引数として指定した変数がインスタンス」とおぼえておいてください。

メソッドの作成

メソッドはコンストラクタとほぼ同じです。コンストラクタでは第1引数にクラス名が渡されるようになっていましたが、メソッドでは、第1引数にオブジェクトへのリファレンスが渡されます。

メソッドのテンプレートは次のようになるでしょう。

sub method1{
	my $self = shift;
}

ここで渡されるオブジェクトは、コンストラクタで作成したオブジェクトリファレンスです。

それでは、次のクラスを例にしてメソッドに渡されるオブジェクトリファレンスの正体を調べてみましょう。

package Class;
sub new{
	my $class = shift;
	# オブジェクトを作成して返す
	my $self = {
		X => 10,
		Y => 100,
	};
	return bless $self, $class;
}
sub method1{
	my $self = shift;
	print "$self->{X} : $self->{Y}\n";
}

コンストラクタで作成したオブジェクトへのリファレンスが、method1の第1引数に必ず渡されます。とくに値が変更されていなければ、最後の print 文の出力結果は 「10 :100」となります。

コンストラクタ
新しいオブジェクトを作成し、そのリファレンスを返すサブルーチン
インスタンス
オブジェクトに属する変数

クラスを利用しよう!

クラスを利用する方法は、ほとんどモジュールの利用法と変わりません。ただし、モジュールのようにメソッドを直接呼び出したりするよりは、クラスから返されるオブジェクトリファレンスを通して呼び出します。それでは、詳しく見ていきましょう。

クラスを呼び出す

このクラスを外部から呼び出すときは、次のようにします。

# クラス名 Class をインポート
use Class;
# クラスのオブジェクトリファレンスを作成
my $obj = new Class;

上記の例の new は、クラス名 Classのコンストラクタで、オブジェクトリファレンスを返しています。ここでは引数を設定していませんが、デフォルトで呼び出しているクラスのパッケージ名が渡されています。

任意の引数を渡す場合は次のようにします。

$obj = new Circle 10, 20;

以上で、Circleクラス作成し、呼び出すことができるようになりました。Circleクラスを呼び出すと、最初にnewが自動的に呼び出され、リファレンスが返されます。上記の例では$obj変数に、返されたリファレンスを代入しています。

メソッドを呼び出す

クラスのオブジェクトリファレンスを受け取ったら、それを利用してメソッドを呼び出してみましょう。

# メソッド method1 の呼び出し
$obj->method1();

扱いはサブルーチンと同じですから、引数を渡すこともできますし、戻り値を受け取ることもできます。

# メソッド名 method1 を呼び出し、戻り値を受け取る
$value = $obj->method1(10, 100);

アクセスメソッドを利用する

オブジェクト指向では、オブジェクトの属性をなるべく隠蔽するように薦められています。具体的には、オブジェクトに属するインスタンス変数へのアクセスをメソッド内だけに限定し、外部ソースから直接アクセスできないようにします。このように、インスタンス変数へのアクセス用に用意されたメソッドをアクセスメソッドと呼びます。アクセスメソッドを利用することにより、オブジェクトの変更による影響を最小限に抑えるメリットが得られます。

インスタンス変数は、次のように外部からアクセスすることができます。

$x = $obj->{X}

アクセスメソッドが用意されている場合は、次のように呼び出すことになるでしょう。

$x = $obj->get_x();

たとえば、インスタンス変数が外部に筒抜けになっている場合は、あらゆる場所で直接操作される可能性があるため、修正箇所が膨大な数になる可能性がありますし、その箇所を見つけるのも困難です。

package Class;
sub new{
	my $class = shift;
	# オブジェクトを作成して返す
	my $self = {
		X => 10;
		Y => 100;
	};
	return bless $self, $class;
}
#------------------------------------------#
package main;
my $obj = new Class;
# インスタンス変数に直接アクセスしている
$obj->{X} = "test A";
$obj->{Y} = "test B";

もし、インスタンス変数の "X"は文字列を与えてはいけないと後々変更したくなった場合、利用しているすべてのプログラムを修正する必要があります。ですが、アクセスメソッドからの利用に限定している場合は、アクセスメソッドを変更するだけでうまく対処できることができます。

今度は、上記と同様の操作を行っていますが、アクセスメソッドを用意してオブジェクトを保護しています。

package Class;
sub new{
	my $class = shift;
	# オブジェクトを作成して返す
	my $self = {
		X => 10,
		Y => 100
	};
	return bless $self, $class;
}
# インスタンス変数へのアクセス用のメソッド
sub modify_obj{
	my ($self, $x, $y) = @_;
	$self->{X} = $x; $self->{Y} = $y;
}
#------------------------------------------#
package main;
$obj = new Class;
# アクセスメソッドからインスタンス変数にアクセス
$obj->modify_obj("test A", "test B");

上記の状態では、インスタンス変数の X と Yは文字列を受け付けます。ただし、アクセスメソッドを次のように変更することで、X と Yは文字列ではないことが保障されるようになります。

# インスタンス変数へのアクセス用のメソッド
sub modify_obj{
	my ($self, $x, $y) = @_;
	# 数値以外は受け付けない
	return $x =~ /\D/ or $y =~ /\D/;
	$self->{X} = $x; $self->{Y} = $y;
}

デストラクタを利用する

bless されたオブジェクトへの作成にはコンストラクタを利用しましたが、その削除にはデストラクタがあります。

クラスに DESTROY メソッドが定義されていると、そのクラスに属する blessされたオブジェクトが破棄される直前に、自動的に DESTROY が呼び出されます。

package Class;
# コンストラクタ定義
sub new {
	my $class= shift;
	bless { x => shift }, $class;
}
# デストラクタ定義
sub DESTROY{
	my $self = shift;
	print "DESTROY > $self->{x}", "\n";
}
#------------------------------------------#
package main;
$a = new Class( '001' );
{
	my $b = new Class( '002' );
}
# $b がスコープ外となり、'002' のオブジェクトが破棄される
# '001' は上書きされ、そのオブジェクトが破棄される
$a = new Class( '003' );

出力結果は次のようになります。

DESTROY > 002    # スコープの外に出た瞬間にDESTROYが呼ばれる
DESTROY > 001    # 上書きされた瞬間にDESTROYが呼ばれる
DESTROY > 003   # スクリプト終了直前にDESTROYが呼ばれる
用語
デストラクタ
クラスに属する blessされたオブジェクトが破棄される直前に、自動的に呼び出されるサブルーチン。オブジェクトがスコープの外に出るか、他のオブジェクトから参照されなくなったときオブジェクトは破棄される。

関連記事