Perl では、フィールドは通常、言語のセマンティクスを通じてこれを強制することによって隠されるのではなく、ドキュメンテーションの形でのコントラクトによって隠されます。ただし、クロージャを使用してフィールドを非表示にすることができます。また、Perl ではクラス メソッドとインスタンス メソッドを意味的に区別していないことにも注意してください。
オブジェクトを実装する標準的な方法の 1 つは、皆さんが行っているように、bless されたハッシュです。このハッシュには、すべてのインスタンス変数/フィールドが含まれます。「プライベート」フィールドはアンダースコアで始めるのが通例です。通常、コントラクト (ドキュメント) には、これらのフィールドがどのように格納されるかは記載されていませんが、クラスのユーザーはさまざまなメソッド呼び出しを行う必要があります。
クラス変数は、インスタンスと共に保存しないでください。グローバル変数またはレキシカル変数を使用することをお勧めします。あなたが与えたコードでは、$count
は単なるカウンターですが、クラス変数としてアクセスすることはありません。代わりに、各インスタンスに一意の ID を割り当てます。クラス変数として使用するには、適切なアクセサーを提供します (s のような不要なものを取り除きましたreturn
)。
{
package Base;
my $count = 0;
sub new {
my ($class) = @_;
my $self = {
ID => $count++,
};
bless $self, $class;
}
sub Count { $count }
sub ID { my ($self) = @_; $self->{ID} }
sub report { my ($self) = @_; "I am the Base object ".($self->ID)."." }
}
=head1 Base
A generic base class
=head2 Base->Count
Return the object count.
=head2 $base->ID
Give the unique ID of this object.
=head2 $base->report
Returns a string containing a short description.
=cut
サブクラスには、カウントに干渉するビジネスはありません。$count
これは、外側の中括弧で示される上記の変数のスコープによって強制されます。サブルーチンは、この変数に対するクロージャです。
{
package Sub;
use parent -norequire, qw(Base); # remove `-norequire` if Base in different file
sub new {
my ($class) = @_;
my $self = $class->SUPER::new;
$self->{Name} = undef;
$self;
}
sub Name :lvalue {
my ($self) = @_;
$self->{Name};
}
sub report {
my ($self) = @_;
"I am the Sub object ".($self->ID)." called ".($self->Name).".";
}
}
=head1 Sub
A generic subclass. It subclasses Base.
=head2 $sub->Name [= SCALAR]
Gets or sets the name of $sub.
my $oldname = $sub->Name;
$sub->name = "new name";
=cut
ご覧のとおり、Sub
コンストラクターはBase
初期化子を呼び出してから、新しいフィールドを追加します。クラスメソッドやクラス変数はありません。$count
クラスは、アクセサー クラス メソッドを介する場合を除き、変数にアクセスできません。契約は、POD ドキュメントに記載されています。
(Name
メソッドでは、:lvalue
注釈を使用します。これにより、オブジェクト内の適切なフィールドに単純に割り当てることができます。ただし、引数のチェックはできません。)
テストケース
my $base1 = Base->new; my $base2 = Base->new;
print "There are now " . Base->Count . " Base objects\n";
my $sub1 = Sub->new; my $sub2 = Sub->new;
print "There are now " . Base->Count . " Base objects\n";
$sub2->Name = "Fred";
print $_->report . "\n" for ($base1, $sub1, $base2, $sub2);
版画
There are now 2 Base objects
There are now 4 Base objects
I am the Base object 0.
I am the Sub object 2 called .
I am the Base object 1.
I am the Sub object 3 called Fred.
美しいですね。(ただし$sub1
、そのオブジェクトには名前がありません。)
ドキュメントは で表示できperldoc -F FILENAME
、次のような出力が得られます
Base
A generic base class
Base->Count
Return the object count.
$base->ID
Give the unique ID of this object.
$base->report
Returns a string containing a short description.
Sub
A generic subclass. It subclasses Base.
$sub->Name [= SCALAR]
Gets or sets the name of $sub.
my $oldname = $sub->Name;
$sub->name = "new name";
*nix システムを使用している場合は、より適切にタイプセットするだけです。
v5.12.4 でテスト済み。
編集:裏返しのオブジェクト
インサイドアウト オブジェクトはより優れたカプセル化を提供しますが、それらは悪い考えです。理解するのが難しく、デバッグが難しく、継承するのが難しく、解決策よりも多くの問題をもたらします。
{
package Base;
my $count = 0;
sub new { bless \do{my $o = $count++}, shift }
sub Count { $count }
sub ID { ${+shift} }
sub report { my ($self) = @_; "I am the Base object ".($self->ID)."." }
}
{
package Sub;
my @_obj = ();
my $count = 0;
sub new {
my ($class) = @_;
$count++;
$_obj[$count - 1] = +{
parent => Base->new(),
Name => undef,
};
bless \do{my $o = $count - 1}, shift;
}
sub Name :lvalue { $_obj[${+shift}]{Name} }
sub AUTOLOAD {
my $self = shift;
my $package = __PACKAGE__ . "::";
(my $meth = $AUTOLOAD) =~ s/^$package//;
$_obj[$$self]{parent}->$meth(@_)
}
sub report {
my ($self) = @_;
"I am the Sub object ".($self->ID)." called ".($self->Name).".";
}
}
この実装にはまったく同じインターフェイスがあり、同じ出力でテスト ケースを完了します。このソリューションは最適とは言えず、単一の継承のみをサポートし、いくつかの中間処理 (自動ロード、動的メソッド呼び出し) を行いますが、驚くほど機能します。各オブジェクトは実際には、フィールドを含む実際のハッシュを検索するために使用できる ID への参照にすぎません。ハッシュを保持する配列には、外部からアクセスできません。このBase
クラスにはフィールドがないため、オブジェクト配列を作成する必要はありませんでした。
Edit2: コードリファレンスとしてのオブジェクト
さらに悪い考えですが、コーディングするのは楽しいです:
{
package Base;
my $count = 0;
sub new {
my ($class) = @_;
my $id = $count++;
bless sub {
my ($field) = @_;
die "Undefined field name" unless defined $field;
if ($field eq "ID") { return $id }
else { die "Unrecognised name $field" }
}, $class;
}
sub Count { $count }
sub ID { my ($self) = @_; $self->("ID") }
sub report { my ($self) = @_; "I am the Base object " . $self->ID . "." }
}
{
package Sub;
use parent -norequire, qw(Base);
sub new {
my ($class) = @_;
my $name = undef;
my $super = $class->SUPER::new;
bless sub {
my ($field, $val ) = @_;
die "Undefined field name" unless defined $field;
if ($field eq "Name") { defined $val ? $name = $val : $name }
else { $super->(@_) }
}, $class;
}
sub Name { my $self = shift; $self->("Name", @_) }
sub report {
my ($self) = @_;
"I am the Sub object ".($self->ID)." called ".($self->Name).".";
}
}
$sub2->Name("Fred")
ここでは左辺値アノテーションを安全に使用できないため、テスト ケースを に適合させ、それに応じてドキュメントを更新する必要があります。