Как я могу ссылаться на объект при его создании с помощью Perl Class :: Struct? - PullRequest
4 голосов
/ 20 января 2010

Я новичок в объектно-ориентированном Perl, и мне нужно получить доступ к переменной члена того же объекта в другой подпрограмме того же объекта. Пример кода здесь:

use Class::Struct;

struct Breed =>
{
    name  => '$',
    cross => '$',
};

struct Cat =>
[
    name     => '$',
    kittens  => '@',
    markings => '%',
    breed    => 'Breed',
    breed2 => '$',

];

my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                    ** //breed2 => sub { return $cat->breed->name;}**

                  );

print "Once a cat called ", $cat->name, "\n";
**print "(which was a ", $cat->breed->name, ")\n";**
print "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";

Но я не уверен, как использовать это $ cat-> breed-> name в подпрограмме для breed2? Может ли кто-нибудь помочь мне с этим.

Ответы [ 5 ]

3 голосов
/ 20 января 2010

Проблема в breed2 состоит в том, что вы пытаетесь обратиться к переменной, которую вы еще не определили. Похоже, это то же имя, но это не тот объект, который вы создаете. Это проблема курицы и яйца.

Я не уверен, что вы хотите анонимную подпрограмму, подобную этой, в любом случае. Ты просто пытаешься сократить $cat->breed->name до $cat->breed2? Вы можете начать с undef в breed2 и изменить его значение сразу после конструктора, так как тогда у вас будет ссылка на объект. Однако, даже если вы поместите подпрограмму туда, вы должны разыменовать ее:

my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                    breed2   => undef,

                  );
$cat->breed2( sub { $cat->breed->name } );

print "Once a cat called ", $cat->name, "\n";
print "(which was a ", $cat->breed2->(), ")\n";
print "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";
2 голосов
/ 20 января 2010

Вы не можете использовать $ cat-> breed-> name внутри конструктора Cat. Но вы можете определить breed2 () как метод после конструктора:

sub Cat::breed2 {
    my ($self) = @_;
    return $self->breed->name;
}
1 голос
/ 21 января 2010

Вы можете исправить это несколькими способами, вот два из них:

use warnings;
use strict;

sub say {print @_, "\n"}

use Class::Struct;

struct Breed =>
{
    name  => '$',
    cross => '$',
};

struct Cat =>
[
    name     => '$',
    kittens  => '@',
    markings => '%',
    breed    => 'Breed',
    breed2   => '$',

];

sub Cat::breed_name {shift->breed->name}  #create a new accessor method

my $cat; # or declare $cat first
$cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                    breed2 => sub { return $cat->breed->name;},
                    # this is now ok, but a bit awkward to call
                  );

print "Once a cat called ", $cat->name, "\n";
print "(which was a ", $cat->breed2->(), ")\n";  #returns then calls code ref
print "(which was a ", $cat->breed_name, ")\n";  #new accessor method
print "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";

Причина, по которой ваше закрытие не сработало правильно, заключается в том, что вы не можете закрыть переменную, определенную в текущем операторе. Когда sub {...} попытался закрыться вокруг $cat, он не смог, потому что он еще не был в области видимости. Решение состоит в том, чтобы просто объявить переменную.

Однако, похоже, что Class::Struct не позволяет правильно устанавливать методы. Вместо этого добавление нового метода доступа в пакет Cat:: позволяет вызывать метод, как и следовало ожидать.

1 голос
/ 20 января 2010

Сначала я начну с нескольких комментариев, а затем перейду к сути вашего вопроса.

OO Perl немного отличается от других систем OO. Существует очень тонкий слой базовой поддержки ОО, который позволяет вашим объектам делать практически все, что вы захотите. С другой стороны, вы можете заставить ваши объекты делать все, что вы хотите. Классический OO Perl включает в себя много стандартного кода, поскольку вы реализуете аксессоры и мутаторы для каждого атрибута, возможно, добавляете проверку типов и так далее. Это привело к появлению широкого спектра инструментов для автоматизации производства шаблонного кода.

Есть три способа, которыми я подхожу к OO Perl: Moose, классический хэш, основанный на ручном кодировании, и Class :: Struct. Moose отлично подходит для систем, где у вас есть сложные потребности, но он сильно влияет на время запуска приложения. Если время запуска важно для вашего приложения, о Moose пока не может быть и речи. Class :: Struct - отличный способ собрать наименьший общий знаменатель, быстрое и простое ОО-приложение вместе, с другой стороны, оно не поддерживает наследование. Вот тут-то и приходит ООП с ручным кодированием. Если Moose или Class :: Struct не являются жизнеспособными вариантами по тем или иным причинам, я возвращаюсь к основам. Эта стратегия хорошо сработала для меня. Единственное изменение, которое я почувствовал необходимость сделать за последние несколько лет, - это добавить Moose в мой стандартный инструментарий. Это долгожданное дополнение.

Объектно-ориентированный Perl Дамиана Конвея - это удивительная книга, которая ясно объясняет ООП, как работает ОО-Perl и как создавать объекты, которые могут делать удивительные вещи. Это немного устарело, но книга все еще держит. Любой серьезный ученик OO Perl должен прочитать эту книгу.

Теперь, на ваш вопрос -

Мне кажется, что breed2 - это не атрибут вашего объекта, а метод.

use Class::Struct;
use strict;
use warnings;

struct Breed =>
{
    name  => '$',
    cross => '$',
};

struct Cat =>
[
    name     => '$',
    kittens  => '@',
    markings => '%',
    breed    => 'Breed',
];

my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                  );

# Delegate to Breed::name
sub Cat::breed2 {
    my $self = shift;

    my $breed = $self->breed;  # Get the breed object

    my $name;

    eval { $name = $breed->name(@_) };

    warn "No breed specified for ".( $self->name )."\n"
        unless defined $name;

    return $name;
}

print  "Once a cat called ", $cat->name, "\n",
       "(which was a ", $cat->breed2, ")\n",
       "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";

Вещи становятся немного неприятнее, если вы хотите сохранить набор предопределенных пород, и breed2 выбирает объект породы по имени, если значение не установлено.

Эта упрощенная реализация Cat использует данные класса для отслеживания разрешенных пород кошек, а

package Cat;
use strict;
use warnings;
use Carp qw( croak );

my %breeds = map { $_->{name}, Breed->new( %$_ ) } (
    { name=>'short-hair', cross=>1 },
    { name=>'long-hair', cross=>1 },
    { name=>'siamese', cross=>0 },
);

sub new {
    my $class = shift;
    my %args = @_;

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

    for my $arg ( keys %args ) {
        $self->$arg( $args{$arg} ) if $self->can($arg);
    }

    return $self;
}

sub breed {
    my $self = shift;
    if( @_ ) {
        my $v = shift;
        croak "Illegal cat breed" unless eval {$v->isa( 'Breed' ) };
        $self->{breed} = $v;
    }

    return $self->{breed};
}

sub breed2 {
    my $self = shift;

    my @breed_args;

    if( @_ ) {
        my $v = shift;

        croak "$v is not a supported breed\n" 
            unless exists $breeds{$v};

        @breed_args = ( $breeds{$v} );
    }

    my $breed = $self->breed(@breed_args);

    return unless $breed;
    return $breed->name;
}

Теперь давайте рассмотрим решение Moose, в котором используются всевозможные дополнительные возможности, такие как приведение типов и перегрузка:

BEGIN {
    package Breed;
    use Moose;

    has 'name'  => ( isa => 'Str',  is => 'ro', required => 1 );
    has 'cross' => ( isa => 'Bool', is => 'ro', required => 1 );

    use overload '""' => \&_overload_string;

    sub _overload_string {
        my $self = shift;

        return $self->name;
    }

    __PACKAGE__->meta->make_immutable;    
    no Moose;
    1;
}

BEGIN {
    package Cat;

    use Moose;
    use Moose::Util::TypeConstraints;
    use Carp;

    subtype 'MyTypes::CatBreed' => as class_type('Breed');

    coerce 'MyTypes::CatBreed' => 
        from  'Str' 
        => via  { Cat->supported_breed_by_name( $_ ) };



    has 'name'     => ( isa => 'Str',  is => 'rw', required => 1 );
    has 'kittens'  => ( 
        traits      => ['Array'],
        is          => 'ro',
        isa         => 'ArrayRef[Str]',
        default     => sub{ [] },
        handles     => {
           all_kittens   => 'elements',
           add_kittens   => 'push',
           get_kitten    => 'get',
           count_kittens => 'count',
           has_kittens   => 'count',
       },
    );
    has 'markings' => ( 
        traits      => ['Hash'],
        is          => 'ro',
        isa         => 'HashRef[Str]',
        default     => sub{ {} },
        handles     => {
            set_marking    => 'set',
            get_marking    => 'get',
            has_marking    => 'exists',
            all_markings   => 'keys',
            delete_marking => 'delete',
        },
    );
    has 'breed'    => ( 
        isa    => 'MyTypes::CatBreed', 
        is     => 'ro', 
        coerce => 1,
    );

    my %breeds;
    sub supported_breed_by_name {
        my $class = shift;
        my $name  = shift;

        croak 'No breed name specified' 
            unless defined $name and length $name;

        return $breeds{$name};
    }

    sub add_breed {
        my $class = shift;
        my $breed  = shift;

        croak 'No breed specified' 
            unless eval { $breed->isa('Breed') };

        croak 'Breed already exists'
            if exists $breeds{$breed};

        $breeds{$breed} = $breed;

        return $class;
    }

    sub delete_breed {
        my $class = shift;
        my $name  = shift;

        croak 'No breed name specified' 
            unless defined $name and length $name;

        return delete $breeds{$name};
    }

    __PACKAGE__->meta->make_immutable;    
    no Moose;
    1;
}


# Set up the supported breeds
Cat->add_breed($_) for map Breed->new( %$_ ), (
    { name=>'short-hair', cross=>1 },
    { name=>'long-hair', cross=>1 },
    { name=>'siamese', cross=>0 },
);

# Make a cat
my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => 'short-hair',
);

print 
    "Once a cat called ", $cat->name, "\n",
    "(which was a ", $cat->breed, ")\n",
    "had ", , " kittens: ", join(' and ', @{$cat->kittens}), "\n";
1 голос
/ 20 января 2010

Не используйте Class :: Struct use Moose .

package Breed;
use Moose;
has 'name'  => ( isa => 'Str', is => 'ro', required => 1 );
has 'cross' => ( isa => 'Bool', is => 'ro' );

package Cat;
use Moose;
has 'name'     => ( isa => 'Str', is => 'ro', required => 1 );
has 'kittens'  => ( isa => 'ArrayRef[Cat]', is => 'ro' );
has 'markings' => ( isa => 'HashRef', is => 'ro', default => sub { +{} } );
has 'breeds'   => ( isa => 'ArrayRef[Breed]', is => 'ro' );

package main;
use Modern::Perl;
my $cat = Cat->new({
  name       => 'Socks',
  , kittens  => [ Cat->new({name=>'Monica'}), Cat->new({name=>'Kenneth'}) ]
  , markings => { socks=>1, blaze=>"white" }
  , breeds   => [ Breed->new({ name=>'short-hair', cross => 1 }) ]
});

say "Once a cat called ", $cat->name;
say "Which was a:";
say "\t".$_->name for @{$cat->breeds};
say "had kittens:";
say "\t".$_->name for @{$cat->kittens};

В этой схеме у кошки может быть любое число Breed с, а у Cat может быть любое число kittens, которые также являются объектами Cat.

обновление специально для решения вашей проблемы

  1. Вы можете сделать это неявным в конструкторе, вторая порода будет первой, если она не предоставлена.

    package Cat; sub BUILD { my $self = shift; $self->breeds->[1] = $self->breeds->[0] if $self->breeds->[0] && ! $self->breeds->[1] }

  2. Вы можете передать токен, который идентифицирует его как таковой, в конструкторе (это должно быть легко, но я могу добавить пример, если хотите)

  3. Вы можете заставить Cat понять, что если есть только одна порода, то оба родителя одинаковы

    package Cat; sub is_pure_bred { length @{$_[0]->breeds} == 1 ? 1 : 0 }

  4. Вы можете игнорировать породу кошки, установив ее на undef, и определить породу по родителям. Это потому, что ваша порода всегда зависит от вашей линии. Вы можете ограничить это в Moose trigger, для кота требуется либо два родителя, либо для породы.

сноска Объекты Moose тоже неплохо сериализуются с XXX :

... use XXX; YYY $cat;

--- !!perl/hash:Cat
breeds:
  - !!perl/hash:Breed
    cross: 1
    name: short-hair
kittens:
  - !!perl/hash:Cat
    markings: {}
    name: Monica
  - !!perl/hash:Cat
    markings: {}
    name: Kenneth
markings:
  blaze: white
  socks: 1
name: Socks
...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...