4

私は perl にかなり慣れていないので、宿題の問題で立ち往生しています。作成されたインスタンスの数をカウントするクラス変数を持つオブジェクトがあります。次に、インスタンス変数を持つサブクラスがあります。

私の最初の質問は、クラス変数をユーザーから非表示にするにはどうすればよいですか? クロージャーを使用してみましたが、継承を機能させる方法がわかりませんでした。そして、それがクラス変数であるという事実は、それをインクリメントするコードが2回実行され、1つあるのに2つのインスタンスがあると言ったため、さらに悪化しました。なぜそれが起こったのか正確にはわかりませんが、それは理にかなっています。スカラーを使用してみましたが、変数が正しくインクリメントされませんでした。「裏返しのオブジェクト」はまだ試していませんが、やりたいかどうかはわかりません。クラス変数をカプセル化することは、インスタンス変数をカプセル化することとは異なると感じていますが、それを行う方法を説明するものは何も見つかりません。

2 番目の質問は、前述したように、カプセル化を継承で機能させることができないということです。サブクラスからスーパー コンストラクターを呼び出すときにクロージャーを使用すると、サブルーチンへの参照が正しく取得されるため、(私が知っている)インスタンス変数をそれに追加する方法はありません。

これが私の基本クラスです:

#!/usr/bin/perl -w
use strict;
package Base;

my $count = 1;

sub new {
    my $class = shift;
    my $self = {
        _Count => $count # not hidden
    };
    $count++; # increment count
    bless $self, $class;
    return $self;
}

sub Count { # getter
    my $self = shift;
    return $self->{_Count};
}
1;

ここに私のサブクラスがあります:

#!/usr/bin/perl -w
use strict;
package Sub;
use Base;
our @ISA = qw(Base);

sub new {
    my $class = shift;
    my $self = $class->SUPER::New();
    $self->{_Name} = undef; # not hidden
    return $self;
}

sub Name { #getter/setter
    my($self, $name) = @_;
    $self->{_Name} = $name if defined($name);
    return $self->{_Name};
}
1;
4

5 に答える 5

4

(OO フレームワークを使用するのではなく) ベア Perl 5 を使用している場合、クラス変数を実行する通常の方法は、アクセサーのみが参照できる字句としてです。

{
    my $count = 0;

    sub Count {
        my ($self, $new_count) = @_;

        if (defined $new_count) { # NB only works if undef is not a legit value
            $count = $new_count;
        }

        return $count;
     }
}

$count囲んでいるブロックでのみ表示されます。同じクラスの他のメソッドでさえ、それを見ることはできません。しかし、誰でも$base_obj->CountまたはBase->Countで操作でき、そのような操作は共有変数に影響します。

クロージャーを使用して、本当に隠されたインスタンス変数を提供することもできます。宿題の恣意的なルールを満たしていない限り、これを行う価値はありません。

package Base;

sub new {
    my ($class, $name) = @_;
    die "Need name!" unless defined $name;

    my $age;

    return bless sub {
        my ($attribute, @args) = @_;

        if ($attribute eq 'name') {
            if (@args) {
                die "Attempt to set read-only attribute!";
            }
            return $name;
        }

        if ($attribute eq 'age') {
            if (@args) {
                 ($age) = @args;
            }
            return $age;
        }

        die "Unknown attribute $attribute";
    } => $class;
}

sub name {
    my ($self, @args) = @_;

    return $self->(name => @args);
}

sub age {
    my ($self, @args) = @_;

    return $self->(age => @args);
}

ここで何が起こるかというと、 によって返された bless されたサブルーチンが2 つのレキシカルをnew閉じていることです。返されると、これらのレキシカルは範囲外になり、その時点からそれらにアクセスする唯一の方法はクロージャーを使用することです。クロージャーは引数を調べて、保持している値へのアクセスを許可または拒否できます。参照を返さない限り、これらの変数への直接アクセスのみを確実に行うことができます。$name$agenew

これは継承でも機能しますが、あまり微妙な追加はありません。

package Derived;
use base 'Base';
sub new {
    my ($class, $name, $color) = @_;

    my $base_instance = $class->SUPER::new($name);

    return bless sub {
        my ($attribute, @args) = @_;

        if ($attribute eq 'color') {
            if (@args) {
                 ($color) = @args;
            }
            return $color;
        }

        # base class handles anything we don't, possibly by dying
        return $base_instance->($attribute, @args);
    } => $class;
}

これは、基本クラスと派生クラスのインスタンス データの個別のストレージを持つ言語が行うことをエミュレートし、リクエストをローカルで処理するか、クロージャーに追加された基本クラスのインスタンスに渡します。より深い継承ツリーは、クロージャーを閉じるクロージャーを閉じるクロージャーになり、それぞれのクロージャーはオプションで、その特定のクラスに必要なインスタンス変数を閉じることもできます。

これは生成するのが非常に面倒で、検査とデバッグが非常に難しいため、これを行うべきではないことをもう一度強調します。しかし、理解することは非常に役に立ちます。そのために、SICPを紹介します。

于 2012-12-07T21:08:43.690 に答える
3

引用するperldoc perlmodlibと、「Perlは、C ++、Ada、Modula-17などの他の言語で慣れているように、モジュールのプライベート部分とパブリック部分を強制しません。Perlには、強制されたプライバシーに夢中になることはありません。散弾銃を持っているからではなく、招待されなかったので、あなたは居間から離れていました。」

Perlの標準的な規則では、すべてを$selfハッシュに入れ、アンダースコアプレフィックスを使用して、どのアイテムをプライベートとして扱う必要があるかを示します。次に、クラスのユーザーがその指示を尊重することを信頼します。同じ規則がメソッドにも適用されます。私のモジュールの1つを使用していて、内部を覗いて内容を$self直接変更するか、電話$obj->_some_private_methodをかけることを選択した場合、森の中に出て何かを壊す可能性があります。または、アップグレードすると、このバージョンで正常に機能するものが壊れることがあります。次のバージョンへ。それが起こった場合、あなたは両方の部分を保持することができます。

クラス外の誰もがデータにアクセスできないようにすることを主張する場合は、それを行う方法がありますが、a)複雑さが増し、ほとんどの場合、不要になります。b)すでに見てきたように、それらは、継承を処理するのがはるかに面倒になる傾向があります。

それでは、あなたへの私の質問は、実際に何を達成しようとしているのか、そしてなぜオブジェクトデータをSooper-Sekretにして完全にアクセスできないようにする必要があると感じるのですか?そうすることでどのようなメリットが得られますか。それは、プライベートとして扱う必要があると思うものにマークを付け、他の人にそれらをそのままにしておくことを信頼するだけでは得られません(他の方法で行う正当な理由がない限り)。

于 2012-12-07T22:38:38.030 に答える
3

モジュールローカルmy変数として$count、モジュール/クラスのユーザーからはすでに非表示になっています。インスタンス変数_Countを「現在のID」型変数として使用しているように見えるため、作成された各オブジェクト(インスタンス)は1から始まる新しいIDを取得します(代わりに、アクティブなインスタンスの数を追跡することを目的としている場合は、デクリメントする必要がありDESTROY、オブジェクトにコピーを保存する必要はありません。)テストコードが1つのインスタンスのみを作成している場合、そのCount()メソッドは1を返す必要が$countありますが、1として開始され、保存後にインクリメントされたため、2になります。オブジェクトの古い値。

perlでは、インスタンス変数を$self非表示にせずにハッシュに格納するのが一般的ですが、衝突を避けるためにプレフィックスが使用されることもあります。これらは、言語機能よりも慣例によって保護されています(実装の詳細に依存することは、変更される可能性があるため安全ではありません)。

perlクラスをより高度に制御したい場合は、Mooseのモジュールスイートをご覧ください。

于 2012-12-07T21:21:01.867 に答える
3

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")ここでは左辺値アノテーションを安全に使用できないため、テスト ケースを に適合させ、それに応じてドキュメントを更新する必要があります。

于 2012-12-07T21:36:50.970 に答える
2

まず、「ユーザーから隠されている」とはどういう意味か正確にはわかりませんが、パッケージ スコープの変数 ( our ) とインスタンス スコープを探しているようです。

package MyBaseClass;
use warnings;
use strict;

our $counter = 0;
sub new { 
    my $class = shift;
    $counter++;
    return bless {}, $class;
}
sub howManyInstances {
    return $counter;
}
1;

2 番目の質問では、クロージャーが継承とどのような関係があるのか​​ わかりません。以下は単純なサブクラスです。

package MySubClass;
use warnings;
use strict;
use parent 'MyBaseClass';  # use parent schema, don't mess with @ISA
sub new {
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{_name} = undef;  
    return $self;
}
# Your setter/getter looks ok as is, though lowercase is tradional for methods/subs
1;

さて、これが実際のコードである場合、このようにはしないでしょう - MooまたはMooseを使用するでしょう。

于 2012-12-07T21:23:13.137 に答える