blog20100901

2013/08/20 - プログラミング言語 Perl にまつわる etc. - Perl monger
参考 : perldoc, perldoc.jp, search.cpan.org, perldoc.perl.org ...
「 初めての Perl 第 6 版 」(オライリー・ジャパン発行 ISBN978-4-87311-567-2) 」
「 続・初めての Perl 改訂版 」(オライリー・ジャパン発行 ISBN4-87311-305-9) 」
「 Effective Perl 第 2 版 」(翔泳社発行 ISBN978-4-7981-3981-4) 」 ... etc,.

Perl Perl_6 オブジェクト指向プログラミング

Perl オブジェクト指向プログラミング (OOP) 基礎知識 (d067)

Perl オブジェクト指向プログラミング (OOP) 基礎知識 (d067)

目次 - Perl Index


Theme



Perl について、復習を兼ねて断片的な情報を掲載して行く連載その d067 回。

Perl で、「 オブジェクト指向プログラミング 」( Object-Oriented Programming : OOP ) を行うための基礎知識を perlmod および perlootut から確認する。

 ・オブジェクト指向の基本
 ・オブジェクト ( object )
 ・クラス ( class )
 ・ブレス ( bless )
 ・コンストラクタ ( constructor )
 ・メソッド ( method )
 ・属性 ( attribute )
 ・多態性 ( polymorphism )
 ・継承 ( inheritance )
 ・ メソッドのオーバーライド ( override ) とメソッド解決 ( method resolution )
 ・ カプセル化 ( encapsulation )
 ・ 包含 ( composition )
 ・ ロール ( role )


オブジェクト指向の基本



Perl に限らず「 オブジェクト指向 」( Object-Oriented : OO ) を実現するシステムは、概ね共通した概念や用語を利用します。

なかでも、「 class 」( クラス ),「 object 」( オブジェクト ),「 method 」( メソッド ),「 attribute 」( 属性 ) といった用語 ( terms ) は定番です。

今回は、Perl の実装を通じて、これらオブジェクト指向プログラミングのための用語を確認します。

なお、以下の内容 perlootut - perldoc.jp をなぞったものなので、正確な内容は perlootut を参照してください。


オブジェクト ( object )



オブジェクト指向の中心にある「 object 」は、「 データ 」とデータを操作する「 サブルーチン 」をひとつにまとめた「 データ構造 」( data structure ) です。

このとき、object が持っているデータを「 attribute 」( 属性 ) と呼び、サブルーチンを「 method 」( メソッド ) と呼びます。

object は「 名詞 」と考えられるといいます。 つまり object は、「 人 」,「 コンピュータ 」,「 ファイル 」といったものをデータ構造で表します。

例えば「 ファイル 」を表す object はデータとして、つまり attribute として次のようなものを持っていると考えられます。

 ・システムパス
 ・コンテンツ
 ・最終更新時刻 ( mtime )

これらのデータを操作するサブルーチンとして、つまり method としては、少なくとも次のようなものを持っているはずです。

 ・rename()
 ・write()

Perl の object の中身は、多くの場合ハッシュ変数で実装されますが、通常 object の内部構造は外部に対して「 不透明 」( opaque ) であることが推奨されています。

「 不透明である 」ということの意味は正確につかめていませんが、今のところは、ユーザが内部構造を意識する ( 推測する ) 必要なく利用出来る設計だと理解しておきます。


クラス ( class )



「 class 」は、object のカテゴリの振る舞いを定義したもので、class の名前は例えば「 File 」のようにカテゴリを表すものになります。反対にいえば、すべての object は class に属しています。

object は、class を通じて「 構築 」( construct ) します。

この処理は「 インスタンス化 」( instantiate ) ともいわれ、構築された object はその class の「 instance 」( インスタンス ) とも呼ばれます。

Perl には class のための特別な構文は用意されていませんが、「 package 」(d058) がその役割を果たします。

Perl の package が class なのかそうでないのかは、package の使われ方によって決まります。つまり、package が method として振る舞うサブルーチンを提供するなら、その package は OO システムの class として扱われます。

Perl で class 「 File 」を宣言するには、つぎのようにディレクティブ「 package 」を利用します。


package File;



また、Perl には多くの OO 言語で用意されている object を構築するための constructor ( コンストラクタ ) 専用のキーワードもありません。

しかし、Perl の世界では名前を「 new 」としたメソッドを用意して、それを constructor として利用することが通例になっています。


# new() で新しい File オブジェクトを構築しつつ初期化している
my $hostname = File->new (
path => '/etc/hostname/',
content => "foo\n",
last_mod_time => 1304974868,
);




ブレス ( bless )



object は「 データ 」とデータを操作する「 サブルーチン 」を持ったデータ構造体ですが、これらを結び付ける機能を Perl の関数「 bless 」が提供します。

関数 bless は、引数のデータ ( リファレンス ) を class となる package に紐づけます。引数に class name がない場合は、現在の package に紐づけられます。


bless REF,CLASSNAME
bless REF



対象のデータは、関数 bless によって class に紐づけられることで object になるので、しばしば object は「 class に bless されている 」といわれます。

Perl のコアモジュールである「 Scalar::Util 」が提供する関数「 blessed 」を使えば、object がどの class に bless されているかを確認することが出来ます。


use Scalar::Util 'blessed';

# $hash は bless されていないデータ
# $hostname は class File に bless された object

# undef
print blessed($hash);

# File
print blessed($hostname);




コンストラクタ ( constructor )



「 constructor 」( コンストラクタ ) は、新しい object を構築 ( construct ) するための method です。

大抵の OO 言語では、constructor のための特別な構文が用意されていますが、Perl では単なるサブルーチンを constructor として利用します。

constructor の名前には慣習的に「 new 」が使われます。


# class File の新しい object を new()
my $file = File->new( ... );



constructor を具体的にどのように記述するかは別の機会に確認します。


メソッド ( method )



object のデータを操作するために提供されるサブルーチンが「 method 」です。

object を「 名詞 」( 人, コンピュータ, ファイル, etc. ) とすると、method は「 動詞 」と考えることが出来ます。

つまり、object がファイルを表現する場合、method は「 保存する 」,「 表示する 」,「 開く 」等の処理を担います。

例えば、class ( package )「 File 」でファイルのシステムパスを出力する method 「 print_info() 」を考えてみます。


package File;
:
# new() 等の他の method の定義
:
.
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}

# システムパスを set/get するための method
sub path {
my $self = shift;
$self->{path} = shift if @_;
return $self->{path};
}



この method は次のように利用します。


# package main

# class File の object を構築
my $file = File->new;

# method print_info() を利用する
$file->print_info;

__END__
# output
This file is at /etc/hostname



ここで、method「 new 」も method「 print_info 」も矢印演算子 ( arrow operator )「 -> 」で呼び出されていることに注目します。

Perl は「 -> 」で呼び出されたサブルーチンを method として認識し、そのサブルーチンの第 1 引数として「 呼び出し元 」( invocant ) を渡すように手配します。

「 呼び出し元 」は「 -> 」の左側に位置するもので、次の場合は File の object instance である「 $file 」が第 1 引数として method「 print_info() 」渡されます。


# $file がインボカント
$file->print_info;



ですから、method「 print_info 」のコードではスカラ「 $self 」に object instance「 $file 」がセットされます。


# 暗黙の配列 @_ から先頭の値を shift で取得
my $self = shift;



通常のサブルーチンと同じく method に引数を渡すことも出来ます。


sub print_info {

# 第 1 引数の invocant をセット
my $self = shift;

# 第 2 以降の引数をチェックしてセット
my $prefix = @_ ? shift : '';

print $prefix, $self->path, "\n";
}



この場合は、例えば次のように利用します。


# package main

$file->print_info("The file is located at ");

__END__
# output
The file is located at /etc/hostname




属性 ( attribute )



class は「 attribute 」( 属性 ) を定義していて、object を instance 化する際に各 attribute に値をセットします。しばしば attribute は「 property 」( プロパティ ) とも呼ばれます。

例えば File の object は、attribute として「 path 」や「 content 」, 「 size 」等を持っているはずです。

Perl に attribute 専用の構文はありませんが、多くの場合はハッシュ変数の key を利用して attribute を実装します。

各 attribute には accsessor ( アクセサ ) のみを通じてアクセスすることが推奨されています。accsessor は attribute の値をゲットしたりセットしたりするための method です。

accessor の中でも、値をゲットする操作のみを行うものを「 getter 」( ゲッター )、値をセットする操作のみを行うものを「 setter 」( セッター ) といいます。値をセットする setter は別名「 mutator 」( ミューテータ : 変化させるもの ) とも呼ばれるようです。

一般的に attribute は、object を構築する時にのみ値をセット出来る「 read-only 」( 読み込み専用 ) か、いつでも値を変更出来る「 read-write 」( 読み書き可能 ) で定義されます。

attribute の値には数値や文字列等の単純な値だけでなく、他の object もセットすることが出来ます。

例えば、File object の最終更新時刻 ( mtime ) を表す attribute には通常 mtime を表す数値セットしますが、数値の代わりにモジュール「 DateTime 」(0x205) の object をセットすることも出来るということです。

class は、設定可能な attribute を一切公開しないことも可能です。また、attribute や method を持たない class もあるようです。


多態性 ( polymorphism )



「 polymorphism 」( 多態性 ) は、同じ「 API 」( application programming interface ) を異なる class が共有すること意味します。

例えば、 class「 File 」と class「 WebPage 」があった場合、これらは異なる class ですが、同じ API として同名の method 「 print_content() 」を持つことが出来ます。

print_content() を呼び出すユーザは、どちらの method を利用するかを選択する ( 意識する ) 必要はありません。呼び出し元の object instance がどちらの class に属しているかでどの method を利用するかが決められます。

このように、状態によって同じ API が異なる結果を返す 「 polymorphism 」( 多態性 ) という概念は、オブジェクト指向の重要なキーワードであるとのことです。

ポリモーフィズムってなんだ?- オブジェクト思考 もあわせて参照してみてください。


継承 ( inheritance )



「 inheritance 」( 継承 ) は、他の class の method と attribute を新しい class で再利用して扱えるようにします。

inheritance は、既存の class の特殊なバージョンを作る場合に利用します。

例えば、class File を継承した class File::MP3 を作れます。File::MP3 は ( is-a ) File の特殊なバージョンで MP3 形式のファイルに特化した処理を行います。

継承関係は「 親/子 」( parent/child ) や「 スーパー/サブ 」( super/sub )、それから「 基本/派生 」( base/derived ) の関係を持つ class といわれます。

また、子 ( sub ) class は親 ( super ) class と「 is-a 」関係 ( MP3 is a File ) を持っているともいわれます。


+------+ 親, super, base +-----------+
| |------------------->| |
| File | | File::MP3 |
| | 子, sub, derived | |
| |<-------------------| |
+------+ +-----------+




Perl は継承関係を定義する方法をいくつか用意していますが、例えば次のようにモジュール「 parent 」が利用出来ます。


package File::MP3;

use parent 'File';



Perl は class の「 多重継承 」( multiple inheritance )、 つまり、複数の親からの継承を認めていますが、継承関係の管理等の問題から推奨はされていません。

なお、多重継承を必要とする場面では、大抵代替として「 role 」( ロール ) が利用出来るそうです。

複数の親 class を継承する多重継承は推奨されませんが、子 class を複数定義することは問題ありません。


+------+ 継承 +-----------+
| File |----->| File::MP3 |
+------+ +-----------+
| # より特殊な子クラスを作成
| +-------------------------+
+->| File::MP3::FixedBitrate |
| +-------------------------+
| +----------------------------+
+->| File::MP3::Variablebitrate |
+----------------------------+



is-a関係とhas-a関係: 継承と包含- オブジェクト思考 もあわせて参照してみて下さい。


メソッドのオーバーライド ( override ) とメソッド解決 ( method resolution )



親 class を継承した子 class では親 class の method をすべて利用可能になりますが、親 class から継承した method を子 class で独自の実装に書き替えることも可能です。

この書き替えを「 override 」( オーバライド ) と呼びます。override は「 覆す 」や「 乗り越える 」等の意味があります。

例えば、class File の method「 print_info 」で考えます。print_info は次のようなものでした。


sub print_info {

# 第 1 引数の invocant をセット
my $self = shift;

# 第 2 以降の引数をチェックしてセット
my $prefix = @_ ? shift : '';

print $prefix, $self->path, "\n";
}



これを class File::MP3 で override します。


package File::MP3;

use parent 'File';

# override
sub print_info {

my $self = shift;

my $prefix = @_ ? shift : '';

print $prefix, $self->path, "\n";

# print 文を追加 !
print "Its title is ", $self->title, "\n";
}



これによって、class File::MP3 を使った print_info の呼び出しでは、親 class の File では出力されない print 文が追加で出力されます。

継承関係の中でどの method を使うべきかを決定する処理を「 メソッド解決 」( method resolusion ) と呼びます。

Perl では、まず object が属する class ( ここでは File::MP3 ) を見て、method の定義があればそれを使い、定義がなければ親 class ( ここでは File ) の method を使います。

もし File に定義がなく、かつ File がさらに親 class「 DataSource 」を継承している場合は、Perl は継承の「 チェーン 」( chain ) をたどってメソッド解決を試みます。

このように、メソッド解決する際の順序を決める処理を「 メソッド解決順序 」( method resolusion order ) と呼びます。

Perl では、このメソッド解決順序を操作するモジュールとして「 mro 」等が提供されているようです。

子 class から親 class の method を明示的に呼び出すには、疑似クラス ( pseudo-class ) と呼ばれる「 SUPER:: 」を利用します。


package File::MP3;

use parent 'File';

# override しつつ親の print_info を呼び出す
sub print_info {

my $self = shift;

# 疑似クラス SUPER:: を利用
$self->SUPER::print_info();

# print 文を追加
print "Its title is ", $self->title, "\n";



「 継承 」の項で「 多重継承 」は推奨されないことに触れましたが、それはメソッド解決が込み入ったものになってしまうからだそうです。

これらの詳細は perlobj を確認します。


カプセル化 ( encapsulation )



「 encapsulation 」( カプセル化 ) はオブジェクトを不透明にするアイデアで、ユーザがその内部実装を知ることなく class を利用出来るようにします。

encapsulation を使えば、公開する API を内部実装から分離出来るので、後で実装を変更したくなった場合でも API を壊すことなく変更出来るようになります。

また、class が充分に encapsulation されていれば、簡単に subclass 化出来るといいます。

subclass はオブジェクトデータにアクセスするために super class と同じ API を使うのが理想的です。

実のところ subclass 化は encapsulation に違反するそうですが、API の設計がうまく出来ていればいるほど subclass 化の必要性は小さくなるとのことです。

Perl の object の多くは内部でハッシュ変数を利用していますが、encapsulation の原則は内部構造に依存してはいけないというものです。

つまり、object のデータには内部実装のハッシュ変数としてアクセスせずに accessor method を通じてデータにアクセスするべきということです。

なお、推奨されるオブジェクトシステム ( Moose, Moo, Class::Accessor, Class::Tiny, etc. ) では、すべての accessor method を *自動で生成する* とのことです。


包含 ( composition )



「 composition 」( 包含 ) は、ある object が別の object を参照していることを意味します。composition は「 has-a 」関係とも呼ばれます。

先の例で class File の attribute で最終更新時刻 ( mtime ) の値を DateTime の object とすることが可能と説明しましたが、例えばその attribute への accessor である method 「 last_mod_time 」が DateTime の object を返すようなら、それは完璧な composition であるといえるとのことです。

最終更新時刻の他にもシステムパスやコンテンツへの accessor が別の object を返すなら、class File は複数の object の composition になります。


ロール ( role )



「 role 」( ロール, あるいは trait? ) は、class を何らかの「 役割 」( role ) に特化させたものです。

「 ごくおおざっぱにいえば,このロールないしトレートとは,クラスからインスタンスの生成やプロパティの管理を行う部分を取り払ったメソッド集にすぎません。 」( モダンPerlの世界へようこそ - gihyo.jp ) とのことです。

例えば、class として「 Radio 」と「 Computer 」があるとします。

これらは両方とも電源の ON/OFF スイッチがあるので、これをクラス定義でモデル化したいと考えた場合、class「 Machine 」のような共通の親 class に ON/OFF スイッチの method を実装しておいてこれを継承することも出来ます。

しかし、すべてのマシンに必ずしも ON/OFF スイッチがあるわけではないと考えると class Machine に ON/OFF の method を実装するのは良いアイデアとはいえなくなります。

親 class として ON/OFF スイッチ専用の class「 HasOnOffSwitch 」を作ることも出来ますが、Radio も Computer もこの親 class を特殊化したものではないので、親子関係として不自然になります。

ここで、「 role 」を利用します。

method として「 turn_on() 」と「 turn_off() 」といった AIP を提供する「 HasOnOffSwitch 」という role を作成して class Radio と Computer に *適用* します。

role は class の継承関係とは別に管理されるので、継承関係に関わらず任意の class や method に適用 ( applied, 取り込む (consume) ともいう ) して利用します。

Perl には role を表現する仕組みが組み込まれていませんが、CPAN にはそれを実現する選択肢 ( Moose 等 ) がいくつも用意されているそうです。


NEXT



Perl の OO システムのパラダイムは「 クラスベース 」 ( Java, C++, C#, Python, Ruby 等と同じ )。JavaScript は「 プロトタイプベース 」。

次回は、perlobj の内容を確認する予定です。


参考情報は書籍「 続・初めての Perl 改訂版 」, 「 Effective Perl 第 2 版 」を中心に perldoc, Wikipedia および各 Web サイト。それと詳しい先輩。

目次 - Perl Index


































同じカテゴリー(Perl)の記事
 Perl mp2 翻訳 Web コンテンツ圧縮の FAQ (d228) (2023-10-11 23:49)
 Perl mp2 翻訳 既知のブラウザのバグの回避策をいくつか (d227) (2023-05-26 15:41)
 Perl mp2 翻訳 Perl と Apache でのキュートなトリック (d226) (2023-05-19 17:05)
 Perl mp2 翻訳 テンプレートシステムの選択 (d225) (2022-08-15 22:23)
 Perl mp2 翻訳 大規模 E コマースサイトの構築 (d224) (2022-06-15 20:43)
 Perl mp2 翻訳 チュートリアル (d223) (2022-06-15 20:42)
上の画像に書かれている文字を入力して下さい
 
<ご注意>
書き込まれた内容は公開され、ブログの持ち主だけが削除できます。

Llama
リャマ
TI-DA
てぃーだブログ
プロフィール
セラ (perlackline)
セラ (perlackline)
QRコード
QRCODE
オーナーへメッセージ

PAGE TOP ▲