2015-09-14 3 views
6

Использование Perl и Moose, доступ к объектным данным можно получить двумя способами.Запись в атрибуты только для чтения внутри класса Perl Moose

$self->{attribute} или $self->attribute()

Вот простой пример, демонстрирующий, как:

# Person.pm 
package Person; 

use strict; 
use warnings; 
use Moose; 

has 'name' => (is => 'rw', isa => 'Str'); 
has 'age' => (is => 'ro', isa => 'Int'); 

sub HAPPY_BIRTHDAY { 
    my $self = shift; 
    $self->{age}++; # Age is accessed through method 1 
} 

sub HAPPY_BIRTHDAY2 { 
    my $self = shift; 
    my $age = $self->age(); 
    $self->age($age + 1); # Age is accessed through method 2 (this will fail) 
} 

1; 

# test.pl 
#!/usr/bin/perl 

use strict; 
use warnings; 
use Person; 

my $person = Person->new(
    name => 'Joe', 
    age => 23, 
); 

print $person->age()."\n"; 

$person->HAPPY_BIRTHDAY(); 
print $person->age()."\n"; 

$person->HAPPY_BIRTHDAY2(); 
print $person->age()."\n"; 

Я знаю, что, когда вы находитесь за пределами файла Person.pm, лучше использовать $person->age() так как это мешает вам совершать немые ошибки и не позволит вам переписать значение только для чтения, но мой вопрос ...

Внутри из Person.pm это лучше всего использовать $self->{age} или $self->age()? Является ли неправильной практикой перезаписывать атрибут только для чтения в пределах сам модуль?

Если этот атрибут должен быть изменен на атрибут чтения/записи, если его значение когда-либо ожидается изменить или считается приемлемым для переопределения аспекта атрибута только для чтения с использованием $self->{age} в функции HAPPY_BIRTHDAY?

ответ

7

При использовании Лось, лучшие практики всегда использовать созданные методы доступа, даже если они находятся внутри собственного класса объекта. Вот несколько причин:

  1. Методы доступа могут быть перечеркнуты дочерним классом, который делает что-то особенное. Вызов $self->age() гарантирует, что будет вызван правильный метод.

  2. Могут быть модификаторы метода, такие как before или after, прикрепленные к атрибуту. Доступ к хэш-значению напрямую будет пропущен.

  3. К атрибуту может быть применен предикат или более четкий способ (например, has_age). Мессинг с хэш-значением напрямую смутит их.

  4. Ключи хэши подлежат опечаткам. Если вы случайно скажете $self->{aeg}, ошибка не будет сразу же обнаружена. Но $self->aeg умрет, так как метода не существует.

  5. Последовательность - это хорошо. Нет причин использовать один стиль в одном месте и другой стиль в другом месте.Это делает код более понятным для новичков.

В конкретном случае атрибут только для чтения, вот некоторые стратегии:

  1. сделать ваши объекты действительно неизменны. Если вам нужно изменить значение, создайте новый объект, который является клоном старого с новым значением.

  2. Используйте атрибут только для чтения, чтобы сохранить реальный возраст, а также указать частный метод писатель

Например:

package Person; 
use Moose; 

has age => (is => 'ro', isa => 'Int', writer => '_set_age'); 

sub HAPPY_BIRTHDAY { 
    my $self = shift; 
    $self->_set_age($self->age + 1); 
} 

Обновление

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

package Person; 
use Moose; 

has age  => (is => 'rw', isa => 'Int', lazy => 1, builder => '_build_age'); 
has is_baby => (is => 'rw', isa => 'Bool', required => 1); 

sub _build_age { 
    my $self = shift; 
    return $self->is_baby ? 1 : 52 
} 

Ленивый строитель не вызывается, пока age не доступен, так что вы можете быть уверены, что is_baby будет там.

Установка элемента хэша напрямую будет, конечно, пропустить метод построения.

+0

Отличный ответ @friedo. Один вопрос, однако, что вы делаете, когда хотите установить атрибуты так условно: '$ person -> {age} = $ person-> is_baby()? 1: 52; '. Как вы делаете что-то подобное, следуя правильному формату «Moose»? – tjwrona1992

+0

@ tjwrona1992, в этом случае я бы использовал метод ленивого построителя для атрибута «age». – friedo

+0

Интересно, я посмотрю на ленивые методы построения. – tjwrona1992

4

Я не думаю, что $self->{age} является документированным интерфейсом, поэтому он даже не гарантированно работает.

В этом случае я бы использовать частный писатель, как описано в https://metacpan.org/pod/Moose::Manual::Attributes#Accessor-methods:

has 'weight' => (
    is  => 'ro', 
    writer => '_set_weight', 
); 

Можно даже автоматизировать это с помощью 'rwp' из https://metacpan.org/pod/MooseX::AttributeShortcuts#is-rwp:

use MooseX::AttributeShortcuts; 

has 'weight' => (
    is => 'rwp', 
); 
+0

'$ self -> {age}' будет работать, поскольку объекты 'Moose' являются' HashRef', но я вижу вашу точку зрения. Это похоже на то, что это, вероятно, «лучший способ» делать вещи. – tjwrona1992

+1

@ tjwrona1992 Конечно, они hashrefs, но документировано, что атрибут под названием «возраст» будет храниться под ключ «age»? – melpomene

+0

@melpomene это несколько подразумевается, все знают, что это правда, и он не мог измениться, не нарушая значительного количества кода ... но нет, я не могу найти какой-либо документ, который четко заявляет об этом как факт :) Я считаю это подразумевается, что метакласс по умолчанию также является * минимальным * одним поверх ванильных perl-соглашений, но я признаю нечеткость этого аргумента. – hobbs

1

Исходный код perl не безопасен по типу и не имеет большого количества инкапсуляции, поэтому легко делать безрассудные вещи. Муз накладывает на ваш объект perl некоторую цивилизацию, обменивая безопасность и стабильность для некоторой свободы. Если Moose становится слишком удушающим, основной Perl все еще существует, поэтому есть способы обойти любые законы, которые пытается уловить железный кулак Муса.

После того, как вы положили голову на то, что вы объявили атрибут только для чтения, но хотите его изменить, хотя вы также сказали, что хотите, чтобы он был доступен только для чтения, и в большинстве юниверсов вы заявляете что-то прочитано только потому, что вы не хотите его менять, тогда непременно выполните обновление и обновите $person->{age}. В конце концов, вы знаете, что делаете.