perlootut - Perl でのオブジェクト指向プログラミングのチュートリアル
この文書は 2011 年 2 月に作成されました。
この文書は Perl でのオブジェクト指向プログラミングを紹介します。 これはオブジェクト指向設計の背後にあるコンセプトの概説から始めます。 それから Perl が提供するものの上に構築されている、 CPAN にあるいくつかの OO システムを紹介します。
デフォルトでは、Perl の組み込みの OO システムはとても最小限で; ほとんどの作業を自分でする必要があります。 この最小限主義は 1994 年には十分意味のあるものでしたが、Perl 5.0 から 時が経つにつれて、Perl OO に多くの共通のパターンが見られるようになりました。 幸い、Perl の柔軟性により、Perl OO システムの豊かなエコシステムが 発展しました。
裏で Perl OO がどのように動作するのかを知りたい場合は、perlobj 文書が 本質的な詳細について説明しています。
この文書は、Perl の文法、変数型、演算子、サブルーチン呼び出しの基本について 既に理解していると仮定しています。 まだこれらのコンセプトについてまだ理解していないなら、まず perlintro を 読んでください。 perlsyn, perlop, perlsub 文書も読むべきでしょう。
ほとんどのオブジェクトシステムは多くの共通の概念を共有しています。 おそらく以前に「クラス」、「オブジェクト」、「メソッド」、属性といった 用語について聞いたことがあるでしょう。 概念の理解は、オブジェクト指向のコードを読み書きするよりも遥かに容易です。 既にこれらの用語に親しんでいるでも、この章を流し読みするべきです; なぜなら Perl の OO 実装の用語でそれぞれの概念を説明しているからです。
Perl の OO システムはクラスベースです。 クラスベース OO はかなり一般的です。 これは Java, C++, C#, Python, Ruby およびその他の多くの言語で使われています。 その他のオブジェクト指向パラダイムもあります。 JavaScript は、その他のパラダイムを使っている最も有名な言語です。 JavaScript の OO システムはプロトタイプベースです。
オブジェクト は、データと、そのデータを操作するサブルーチンを一つに まとめたデータ構造です。 オブジェクトのデータは 属性 と呼ばれ、サブルーチンは メソッド と 呼ばれます。 オブジェクトは名詞と考えることができます (人、web サービス、 コンピュータなど)。
オブジェクトは単一のものを表現します。 たとえば、あるオブジェクトがファイルを表現しているとします。 ファイルオブジェクトの属性はパス、内容、最終更新時刻といったものになります。 オブジェクトのパスが "/etc/hostname" になるような、 "foo.example.com" という名前のマシンの /etc/hostname を表現する オブジェクトを作る場合、その内容は "foo\n" で、最終更新時刻は紀元から 1304974868 秒といったものになります。
ファイルに結びつけられたメソッドは rename()
や write()
と いったものになります。
Perl ではほとんどのオブジェクトはハッシュですが、推奨する OO システムは これについて気にする必要がないようにします。 実際には、オブジェクトの内部構造は不透明であると考えるのが最良です。
クラス はあるカテゴリのオブジェクトの振る舞いを定義します。 クラスは("File" のような)カテゴリを表す名前です; またクラスは そのカテゴリのオブジェクトの振る舞いを定義します。
全てのオブジェクトは何らかのクラスに属します。 例えば、/etc/hostname オブジェクトは File
クラスに属します。 特定のオブジェクトを作りたい場合、そのクラスから始めて、オブジェクトを 構築(construct) または インスタンス化 (instantiate) します。 特定のオブジェクトはしばしばクラスの インスタンス (instance) と 呼ばれます。
Perl では、どのパッケージもクラスになれます。 クラスであるパッケージと層でないパッケージの違いは、パッケージがどのように 使われるかによります。 以下は File
クラスのための「クラス宣言」です:
package File;
Perl では、オブジェクトの構築のための特別なキーワードはありません。 しかし、CPAN のほとんどの OO モジュールは、新しいオブジェクトの構築に new()
という名前のメソッドを使います:
my $hostname = File->new(
path => '/etc/hostname',
content => "foo\n",
last_mod_time => 1304974868,
);
(->
演算子について心配しないでください; 後で説明します。)
既に述べたように、ほとんどの Perl オブジェクトはハッシュですが、 オブジェクトはどの Perl データ型 (スカラ、配列など)のインスタンスも可能です。 普通のデータ構造のオブジェクトへの変換は、データ構造に Perl の bless
関数を 使うことによる bless によって行われます。
一からオブジェクトを構築しないことを強く勧めますが、bless という 用語は知っておくべきです。 bless された データ構造 (またの名を「リファレンス先」)はオブジェクトです。 時々、オブジェクトは「クラスに bless された」と表現します。
一旦リファレンス先が bless されると、Scalar::Util コアモジュールの blessed
関数はそのクラス名を返します。 このサブルーチンは、オブジェクトが和されるとオブジェクトのクラスを返し、 さもなければ偽を返します。
use Scalar::Util 'blessed';
print blessed($hash); # undef
print blessed($hostname); # File
コンストラクタ は新しいオブジェクトを作成します。 コンストラクタのための文法を提供しているその他の言語と異なり、Perl では クラスのコンストラクタは単なるメソッドです。 ほとんどの Perl クラスはコンストラクタの名前として new
を使います:
my $file = File->new(...);
オブジェクトを操作するサブルーチンが メソッド であるということは 既に学んでいます。 メソッドはオブジェクトが する ことと考えることができます。 オブジェクトが名詞なら、メソッドは動詞 (保存する、表示する、開く) です。
Perl では、メソッドは単にクラスのパッケージにあるサブルーチンです。 メソッドは常に最初の引数としてオブジェクトを受け取るように書かれます:
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
$file->print_info;
# The file is at /etc/hostname
メソッドを特別なものにしているのは、どのように呼び出されるか です。 矢印演算子 (->
) は、メソッドとして呼び出していることを Perl に 知らせます。
メソッド呼び出しをするとき、Perl はメソッドの 呼び出し元 (invocant) を 最初の引数として渡すように用意します。 to be passed as the first argument. 呼び出し元 とは矢印の左側にあるものの名前です。 呼び出し元はクラス名とオブジェクトのどちらかです。 また、メソッドに追加の引数も渡せます:
sub print_info {
my $self = shift;
my $prefix = shift // "This file is at ";
print $prefix, ", ", $self->path, "\n";
}
$file->print_info("The file is located at ");
# The file is located at /etc/hostname
各クラスは 属性 を定義します。 オブジェクトをインスタンス化するとき、これらの属性に値を代入します。 例えば、各 File
オブジェクトはパスを持ちます。 属性は時々 プロパティ (properties) と呼ばれます。
Perl には属性のための特別な文法はありません。 内部では、属性はしばしばオブジェクトの基となっているハッシュのキーとして 保管されますが、これについて気にすることはありません。
属性には アクセサ (accessor) メソッドを経由してのみアクセスすることを 勧めます。 これはそれぞれの属性の値を取得または設定するメソッドです。 既に、前述した print_info()
の例で、$self->path
を 呼び出しているのを見ています。
ゲッター (getter) と セッター (setter) という用語も見るかも知れません。 これらはアクセサの種類です。 ゲッターは属性の値を取得し、セッターは設定します。 セッターに関する別名は ミューテータ (mutator) です。
属性は典型的には読み込み専用または読み書き可能として定義されます。 読み込み専用属性はオブジェクトが最初に作成されるときにのみ設定でき、 読み書き可能属性はいつでも変更できます。
属性の値それ自身が他のオブジェクトかも知れません。 例えば、最終更新時刻を数値として返す代わりに、File
クラスはその値を 表現する DateTime オブジェクトを返すかも知れません。
設定可能な属性が一切公開されていないクラスも可能です。 全てのクラスが属性とメソッドを持っているというわけではありません。
多態性 (polymorphism) は二つの異なるクラスが API を共有しているということを 示す変わった方法です。 例えば、どちらも print_content()
メソッドを持つ、File
クラスと WebPage
クラスを持てます。 このメソッドはクラスごとに異なった出力を生成するかも知れませんが、共通の インターフェースを共有します。
二つのクラスはいろいろな意味で異なっているかも知れませんが、 print_content()
メソッドを呼び出すときには、これらは同じです。 これは、どちらのクラスのオブジェクトに対しても print_content()
を 呼び出そうとすることができて、オブジェクトがどのクラスに属しているかを 知る必要がないと言うことです!
多態性はオブジェクト指向設計の鍵となる概念の一つです。
継承 は既にあるクラスの特殊版を作成できるようにします。 継承は他のクラスのメソッドと属性を再利用して新しいクラスを 作成できるようにします。
例えば、File
から 継承 した File::MP3
クラスを作成できます。 File::MP3
は (is-a) File
の より特殊な 型です。 全ての mp3 ファイルはファイルですが、全てのファイルが mp3 ファイルというわけではありません。.
継承関係はしばしば 親-子 または スーパークラス/サブクラス
関係として 参照されます。 子は親クラスとの is-a 関係を持つと表現することがあります。
File
は File::MP3
の スーパークラス で、File::MP3
は File
の サブクラス です。
package File::MP3;
use parent 'File';
parent モジュールは、Perl で継承関係を定義するいくつかの方法のひとつです。
Perl は多重継承を認めています; つまり、クラスは複数の親から継承できます。 これは可能ですが、行わないように強く勧めます。 一般的に、多重継承で行えることは全て、ロール を使うことでより きれいな形で行えます。
あるクラスに対して複数のサブクラスを定義することは何も悪くないことに 注意してください。 これは一般的でかつ安全です。 例えば、mp3 ファイルの異なった種類を区別するために、 File::MP3::FixedBitrate
クラスと File::MP3::VariableBitrate
クラスを 定義できます。
継承は二つのクラスでコードを共有できるようにします。 デフォルトでは、親クラスの全てのメソッドは子でも利用可能です。 子は、独自の実装を提供することで親のメソッドを明示的に オーバーライド (override) できます。 例えば、File::MP3
オブジェクトがあれば、File
からの print_info()
メソッドがあります:
my $cage = File::MP3->new(
path => 'mp3s/My-Body-Is-a-Cage.mp3',
content => $mp3_data,
last_mod_time => 1304974868,
title => 'My Body Is a Cage',
);
$cage->print_info;
# The file is at mp3s/My-Body-Is-a-Cage.mp3
返り値に mp3 のタイトルを含めたい場合、メソッドをオーバーライドできます:
package File::MP3;
use parent 'File';
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
print "Its title is ", $self->title, "\n";
}
$cage->print_info;
# The file is at mp3s/My-Body-Is-a-Cage.mp3
# Its title is My Body Is a Cage
どのメソッドが使われるべきかを決定する処理は、メソッド解決 (method resolution) と呼ばれます。 Perl が行うことは、まずオブジェクトのクラス (この場合は File::MP3
) を 見ます。 このクラスにメソッドが定義されていれば、そのクラスのメソッドが呼び出されます。 さもなければ、Perl は順番に親クラスを見ます。 File::MP3
の場合、唯一の親は File
です。 File::MP3
がメソッドを定義しておらず、File
が定義しているなら、 Perl は File
のメソッドを呼び出します。
File
が DataSource
から継承され、これが Thing
から継承されていれば、 Perl はもし必要なら「チェーンをたどって」探し続けます。
子から親メソッドを明示的に呼び出すことは可能です:
package File::MP3;
use parent 'File';
sub print_info {
my $self = shift;
$self->SUPER::print_info();
print "Its title is ", $self->title, "\n";
}
SUPER::
は、File::MP3
クラスの継承チェーンから print_info()
を 探すように Perl に伝えます。 このメソッドを実装している親クラスが見つかると、そのメソッドが 呼び出されます。
以前に多重継承について述べました。 多重継承の主な問題は、メソッド解決が非常に込み入っているということです。 さらなる詳細については perlobj を参照してください。
カプセル化 は、オブジェクトを不透明にする考え方です。 他の開発者があなたのクラスを使うとき、どのように 実装されているかを 知る必要はなく、単に 何を するかを知る必要があるだけです。
カプセル化はいくつかの理由で重要です。 まず、これにより公的な API を内部の実装から分離できます。 これにより、API を壊すことなく実装を変更できます。
次に、クラスが十分にカプセル化されていれば、サブクラス化が容易になります。 理想的には、サブクラスは親クラスが使っているオブジェクトデータに アクセスするのに同じ API を使います。 実際には、サブクラス化は時々カプセル化に違反しますが、よい API は その必要性を最小限にします。
既に、ほとんどの Perl オブジェクトは内部ではハッシュとして実装されていると 述べました。 カプセル化の原則は、このことに依存するべきではないと伝えています。 代わりに、ハッシュのデータにアクセスするためにアクセサメソッドを使います。 後に推奨するオブジェクトシステムは全てアクセサメソッドの生成を自動化します。 これらの一つを使うなら、オブジェクトをハッシュとして直接アクセスする必要は ないはずです。
オブジェクト指向のコードでは、しばしばあるオブジェクトが他のオブジェクトを 参照しています。 これは 包含 (composition)、または has-a 関係と呼ばれます。
既に、File
クラスの last_mod_time
アクセサが DateTime オブジェクトを 返すかも知れないことに触れました。 これは包含の完璧な例です。 さらに進めて、path
と content
アクセサもオブジェクトを返すようにも できます。 そして File
クラスはいくつかのその他のオブジェクトの 包含 になります。
ロール は、クラスが 何か ではなく、クラスが 何をするか です。 ロールは Perl では比較的新しいですが、かなり人気になってきています。 ロールはクラスに 適用 されます。 クラスがロールを 消費 (consume) するということもあります。
ロールは多態性を提供するための継承の代替策です。 二つのクラス Radio
と Computer
があると考えます。 どちらもオン/オフするスイッチがあります。 これをクラス定義でモデル化したいとします。
Machine
のように、共通の親から両方のクラスを継承することもできますが、 全ての機械にオン/オフスイッチがあるわけではありません。 HasOnOffSwitch
と呼ばれる親クラスを作ることもできますが、とても 不自然です。 ラジオとコンピュータはこの親の特殊化ではありません。 この親は実際にはかなりおかしなものです。
これはロールの出番です。 HasOnOffSwitch
ロールを作って両方のクラスに適用することは とても合理的です。 このロールは turn_on()
と turn_off()
メソッドを提供するような 既知の API を定義します。
Perl にはロールを記述する組み込みの方法はありません。 以前は、人々は我慢して多重継承を使っていました。 最近は、ロールを使うためのいくつかの良い選択肢が CPAN にあります。
オブジェクト指向は全ての問題に対する最良の解法というわけではありません。 Perl Best Practices (copyright 2004, Published by O'Reilly Media, Inc.) において、Damian Conway は OO が問題に正しく適応するかどうかを決定するときに 使う基準の一覧を提供しています:
設計しているシステムが大きいか、大きくなりそう。
データが明らかな構造に集合できる; 特にそれぞれの集合が大量のデータを持つ。
様々な型のデータ集合が、継承と多態性を使うことが容易な自然な階層を形成する。
多くの異なった操作を適用するデータがある。
データの種類に関係する同じ一般的な操作をする必要があるが、操作を適用する データの特定の種類に依存して少しずつ変化がある。
後で新しいデータ型を追加する必要がありそう。
データ間の典型的な相互作用は演算子によって最もよく表現される。
システムの独立した要素の実装が時間につれて変化しそう。
システムの設計が既にオブジェクト指向である。
多くのプログラマがこのコードモジュールを使う。
前述したように、Perl の組み込みの OO システムは非常に最小限ですが、 一方とても柔軟です。 年を重ねるにつれて、多くの人々がより多くの機能と利便性を提供するために Perl の組み込みのシステムの上に構築されたシステムを開発しました。
私たちはこれらのシステムの一つを使うことを強く勧めます。 それらの中の最小限のものでも多くの繰り返される定型文を排除できます。 Perl でクラスを一から書く本当にいい理由というものはありません。
これらのシステムの基礎となる内部に興味があるなら、 perlobj を 調べてください。
Moose は自分自身を「Perl 5 のためのポストモダンオブジェクトシステム」と 宣伝しています。 怖がらなくても大丈夫です; 「ポストモダン」というのはラリーが Perl のことを 「最初のポストモダンコンピュータ言語」と説明したことにちなんでいます。
Moose
は完全でモダンな OO システムを提供します。 その最大の影響元は Common Lisp Object System ですが、Smalltalk およびその他の いくつかの言語からもアイデアを借りています。 Moose
は Stevan Little によって作成され、彼の Perl 6 OO 設計に関する 作業から多くを引いています。
以下は Moose
を使った File
クラスです:
package File;
use Moose;
has path => ( is => 'ro' );
has content => ( is => 'ro' );
has last_mod_time => ( is => 'ro' );
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
Moose
は多くの機能を提供します:
Declarative sugar@@@@@@@@@@構文糖
Moose
はクラスを定義するために、宣言的な「糖」の層を提供します。 この糖は、クラスがより簡単に、使いやすくなるようにするための単に エクスポートされた関数の集合です。 これは Perl が どのように クラスを実装するのかを教えるように するのではなく、クラスが 何を するかを表現するようにします。
has()
サブルーチンは属性を定義し、Moose
は自動的にそれらの属性のための アクセサを作成します。 また、new()
メソッドの面倒も見ます。 このコンストラクタは宣言された属性について知っているので、 新しい File
を作成するときにそれを設定できます。
Roles built-in@@@@@@@@@@組み込みのロール
Moose
はクラスを定義するのと同じようにロールを定義できます:
package HasOnOfSwitch;
use Moose::Role;
has is_on => (
is => 'rw',
isa => 'Bool',
);
sub turn_on {
my $self = shift;
$self->is_on(1);
}
sub turn_off {
my $self = shift;
$self->is_on(0);
}
A miniature type system@@@@@@@@@@小型の型システム
前述の例では、is_on
属性を作成するときに has()
に isa => 'Bool'
を渡します。 これは Moose
に、この属性は真偽値でなければならないということを示します。 これに不正な値を設定しようとすると、エラーを投げます。
Full introspection and manipulation@@@@@@@@@@完全なイントロスペクションと操作
Perl の組み込みのイントロスペクション機能はかなり最低限です。 Moose
はこれらの上に構築され、クラスのための完全なイントロスペクション 層を作成します。 これにより、「File クラスに実装されているメソッドは何?」といった問い合わせが できます。 また、クラスをプログラムで修正できます。
Self-hosted and extensible@@@@@@@@@@セルフホスティングと拡張性
Moose
は自分自身を自身のイントロスペクション API を使って記述しています。 かっこいい技という以外に、これは Moose
自身を使って Moose
を 拡張できるということです。
Rich ecosystem@@@@@@@@@@豊かなエコシステム
CPAN の MooseX 名前空間に、Moose
拡張の豊かなエコシステムがあります。 さらに、CPAN の多くのモジュールは既に Moose
を使っていて、そこから学べる 多くの例を提供しています。
Many more features@@@@@@@@@@さらに多くの機能
Moose
はとても強力なツールで、その機能の全てについてここで触れることは できません。 さらに学ぶために、Moose
の文書を Moose::Manual から 読むことを勧めます。
もちろん、Moose
は完璧ではありません。
Moose
によってコードの読み込みが遅くなることがあります。 Moose
自身が小さくなく、またクラスを定義するときに 大量 の コードを生成します。 このコード生成は、実行時コードは可能な限り高速であることを意味しますが、 このためにモジュールが最初に読み込まれるときにコストを支払うことになります。
この読み込み時間は、実行時に毎回読み込みしなければならない コマンドラインスクリプトや「何の変哲もない」CGIスクリプトのように、 起動速度が重要な場合には問題になり得ます。
うろたえる前に、多くの人々がコマンドラインツールやその他の起動時間が重要な コードに Moose
を使っていることを知ってください。 起動速度について気にする前にまず Moose
を試してみることを勧めます。
Moose
はまた他のモジュールに対していくつかの依存があります。 それらのほとんどは小さいスタンドアロンのモジュールで、いくつかは Moose
から切り離されたものです。 Moose
自身と、その依存のいくつかは、コンパイラを必要とします。 コンパイラのないシステムにインストールする必要があったり、どんな 依存があっても問題がある場合は、Moose
は適切ではないかも知れません。
Moose
を試してみて、これらの理由の一つが Moose
を使うのを 妨げているなら、次に Mouse を考慮するのを勧めます。 Mouse
は Moose
の昨日のサブセットをより単純なパッケージで 実装しています。 実装されている全ての機能について、エンドユーザー API は Moose
と 同一 です; つまり Mouse
から Moose
にとても簡単に 切り替えられるということです。
Mouse
は Moose
のイントロスペクション API を実装しておらず、従って モジュールを読み込むときはしばしばより速いです。 さらに、必要な 全ての依存は Perl のコアと共に出荷されていて、 コンパイラなしで動作します。 コンパイラがあれば、Mouse
はコンパイラを高速化のためにコードの一部を コンパイルするために使います。
最後に、Mouse
のほとんどの機能を持ち、一つのモジュールファイルにまとめた Mouse::Tiny
モジュールも同梱されています。 簡単に同梱するために、このモジュールファイルをアプリケーションの ライブラリディレクトリにコピーできます。
Moose
の作者は、いつの日か Moose
が十分改良されることによって Mouse
が古いものになることを望んでいますが、今のところは Moose
に対する 価値のある代替を提供します。
Class::Accessor は Moose
の対極です。 非常に少ない機能しか提供しませんし、セルフホスティングでもありません。
しかし、とても単純で、ピュア Perl で、非コア依存はありません。 また、対応している機能に対する「Moose 風」 API も提供しています。
多くのことはしませんが、それでもクラスを一から書くよりは望ましいです。
以下に Class::Accessor
を使った File
クラスを示します:
package File;
use Class::Accessor 'antlers';
has path => ( is => 'ro' );
has content => ( is => 'ro' );
has last_mod_time => ( is => 'ro' );
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
antlers
インポートフラグは、属性を Moose
風の文法で定義したいことを Class::Accessor
に伝えます。 has
に渡せる唯一の引数は is
です。 Class::Accessor
を選択した場合は、この Moose 風の文法を使うことを勧めます; なぜなら後に Moose
に移行しようと決めたときによりスムーズに アップグレードできるからです。
Moose
と同様、Class::Accessor
はアクセサメソッドとコンストラクタを 生成します。
最後に、Object::Tiny があります。 このモジュールはまさにその名前通りです。 これは非常に最小限の API のみを持ち、(コアかどうかに関わらず)全く依存は ありません。 それでも、一から独自の OO コードを書くよりも遙かに簡単に使えます。
以下に File
クラスをもう一度示します:
package File;
use Object::Tiny qw( path content last_mod_time );
sub print_info {
my $self = shift;
print "This file is at ", $self->path, "\n";
}
これだけです!
Object::Tiny
では、全てのアクセサは読み込み専用です。 コンストラクタと、定義したアクセサを生成します。
前述したように、ロールは継承の代替策を提供しますが、Perl には組み込みの ロール対応はありません。 Moose を使うことを選んだ場合、完全なロール実装が同梱されています。 しかし、その他の推奨 OO モジュールを使う場合、Role::Tiny で ロールを使えます。
Role::Tiny
は Moose のロールシステムと同じような機能を提供しますが、 パッケージは遙かに小さいです。 特に、属性宣言には対応していないので、手動で行う必要があります。 それでも、これは有用で、Class::Accessor
および Object::Tiny
と うまく動作します。
以下はここで取り上げた選択肢の簡潔なまとめです:
Moose
は最大限のオプションです。 多くの機能、大きなエコシステム、繁栄しているユーザーベースがあります。 また、Mouse も簡単に取り上げました。 Mouse
は Moose
の簡略版で、Moose があなたのアプリケーションで 動作しないときの妥当な代替案です。
Class::Accessor
がすることは Moose
よりもとても少なく、Moose
が おおげさな時にはよい選択肢です。 長い間存在し、よくテストされています。 Class::Accessor
から Moose
への以降を容易にするための最小限の Moose
互換モードもあります。
Object::Tiny
は明らかに最小限のオプションです。 依存はなく、学ぶ必要のある文法もほとんどありません。 超最小限環境の場合や詳細について心配することなく素早く何かを作るときには よいオプションです。
多重継承について考えているなら Class::Accessor
または Object::Tiny
と 共に Role::Tiny
を使ってください。 Moose
を使っているなら、独自のロール実装が同梱されています。
ここで扱ったものの他に、CPAN には文字通り数十のその他の OO 関連の モジュールがあり、他の人のコードを動かすときにはおそらく それらのいくつかを使っているでしょう。
さらに、世の中の多くのコードは全ての OO を「手動で」、単に Perl 組み込みの OO 機能を使って行っています。 もしそのようなコードを保守する必要があるなら、Perl の組み込みの OO が どのように動作するのかを正確に理解するためにperlobj を読むべきです。
既に述べたように、Perl の最小限の OO システムは CPAN での豊富な OO システムを生み出しました。 今でもこの地金まで降りていってクラスを手で書くこともできる一方、 モダン Perl においてそうする理由は本当はありません。
小さいシステムなら、Object::Tiny と Class::Accessor の両方は 基本的な定型文の面倒を見る最小限のオブジェクトシステムを提供します。
より大きいプロジェクトなら、Moose はあなたがビジネスロジックの実装に 集中できるような豊富な機能セットを提供します。
どの OO システムが適しているかを見るために、Moose, Class::Accessor, Object::Tiny を試して評価することをお勧めします。