ここまで幾つかの考察をしてきた。
property モジュール全体を書き直してみよう。

プロパティ名、転送先 getter/setter メソッド名は、
継承上の問題により、共に文字列で指定し、
動的に処理する方が相応しいことが判明した。

property モジュールの引数はハッシュリファレンスだが、
そのパラメータは全て文字列で渡されることになる。
例えばプロパティが、'salary' の場合、
getter/setter は、'set_salary', 'get_salary' となる。

全て文字列ということは、プロパティ名に、
'get_', 'set_' の接頭語を追加することで、
getter/setter のメソッド名を推定することができる。

では、property モジュールの引数が文字列の場合、
メソッド名を自動的に推定する機能も追加してみよう。

========== property.pm ==========

package property;

use v5.6.0;
use strict;
use warnings;
use Carp;

BEGIN {
    our $Version = '0.05';
}

sub import {
    my ($package, $param) = @_;

    my $target = caller;

    # $param 引数がスカラの場合推測する。
    if (ref $param eq '') {
        $param = {
            'name' => "$param",
            'get' => "get_$param",
            'set' => "set_$param",
        };
    }

    _create_property($target, $param->{'name'},
            $param->{'get'}, $param->{'set'});

    _trust_me($target);
}

# 対象に対して Carp の信頼基盤を有効にする。
sub _trust_me {
    my ($target) = @_;

    my $symbol = "${target}::CARP_NOT";
    no strict 'refs';
    no warnings;
    push(@$symbol, __PACKAGE__);
}

# プロパティを登録する。
sub _create_property {
    my ($package, $name, $get, $set) = @_;

    my $propertizer = sub : lvalue {
        my $this = shift;
        tie(my $reflector, __PACKAGE__, $this, $get, $set);
        $reflector;
    };

    _register_code($package, $name, $propertizer);
}

# パッケージにコードを登録する。
sub _register_code {
    my ($package, $name, $code) = @_;

    my $symbol = "${package}::${name}";
    no strict 'refs';
    *$symbol = $code;
}

# lvalue マジカルスカラ用の tie 実装。
sub TIESCALAR {
    my ($package, $object, $get, $set) = @_;

    $package = (ref $package or $package or __PACKAGE__);
    my $this = { 'object' => $object, 'get' => $get, 'set' => $set };
    bless($this, $package);
}

# プロパティ値の取得。
sub FETCH {
    my $this = $_[0];

    my $object = $this->{'object'};
    my $method = $this->{'get'};
    my $code = $object->can($method);

    # getter が呼び出せない場合は書き込み専用。
    croak "Can't get value to this property."
            if not defined $code;

    $object->$code();
}

# プロパティ値の代入。
sub STORE {
    my ($this, $value) = @_;

    my $object = $this->{'object'};
    my $method = $this->{'set'};
    my $code = $object->can($method);
    # setter が呼び出せない場合は読み取り専用。
    croak "Can't set value to this property."
            if not defined $code;

    $object->$code($value);
}

1;

========== end of property.pm ==========