我有一个封装Int的类Price.我也希望它有Num和Str的构造函数.我认为我可以通过使Price :: new成为具有各种类型约束的多方法来实现这一点,但这不是我期望的行为.看起来Price.new正在完全跳过构造函数并直接进入BUILD,绕过了构建逻辑.
我知道通过查看其他Perl 6代码,使用multi方法new是可以接受的.但是,我还没有找到具有不同类型约束的多态构造函数的示例.如何重写此代码以强制它在构造函数中使用转换逻辑?
LIB / Price.pm6
#!/usr/bin/env perl6 -w use v6; unit class Price:ver<0.0.1>; class X::Price::PriceInvalid is Exception { has $.price; method message() { return "Price $!price not valid" } } # Price is stored in cents USD has $.price; multi method new(Int $price) { say "Int constructor"; return self.bless(:$price); } multi method new(Num $price) { say "Num constructor"; return self.new(Int($price * 100)); } multi method new(Str $price) { say "String constructor"; $price .= trans(/<-[0..9.]>/ => ''); unless ($price ~~ m/\.\d**2$/) { die(X::Price::PriceInvalid(:$price)); } return self.new(Num($price)); } submethod BUILD(:$!price) { say "Low-level BUILD constructor" } method toString() { return sprintf("%.2f",($!price/100)); }
吨/ price.t
#!/usr/bin/env perl6 -w use v6; use Test; use-ok 'Price','Module loads'; use Price; # test constructor with Int my Int $priceInt = 12345; my $priceIntObj = Price.new(price => $priceInt); is $priceIntObj.toString(),'123.45','Price from Int serializes correctly'; # test constructor with Num my $priceNum = Num.new(123.45); my $priceNumObj = Price.new(price => $priceNum); is $priceNumObj.toString(),'Price from Num serializes correctly'; # test constructor with Num (w/ extra precision) my $priceNumExtra = 123.4567890; my $priceNumExtraObj = Price.new(price => $priceNumExtra); is $priceNumExtraObj.toString(),'Price from Num with extra precision serializes correctly'; # test constructor with Str my $priceStr = '$123.4567890'; my $priceStrObj = Price.new(price => $priceStr); is $priceStrObj.toString(),'Price from Str serializes correctly'; # test constructor with invalid Str that doesn't parse my $priceStrInvalid = 'monkey'; throws-like { my $priceStrInvalidObj = Price.new(price => $priceStrInvalid) },X::Price::PriceInvalid,'Invalid string does not parse'; done-testing;
PERL6LIB的输出= lib / perl6 t / price.t
ok 1 - Module loads Low-level BUILD constructor ok 2 - Price from Int serializes correctly Low-level BUILD constructor not ok 3 - Price from Num serializes correctly # Failed test 'Price from Num serializes correctly' # at t/price.t line 18 # expected: '123.45' # got: '1.23' Low-level BUILD constructor not ok 4 - Price from Num with extra precision serializes correctly # Failed test 'Price from Num with extra precision serializes correctly' # at t/price.t line 24 # expected: '123.45' # got: '1.23' Low-level BUILD constructor Cannot convert string to number: base-10 number must begin with valid digits or '.' in '⏏\$123.4567890' (indicated by ⏏) in method toString at lib/Price.pm6 (Price) line 39 in block <unit> at t/price.t line 30
解决方法
您编写的所有新多方法都采用一个位置参数.
:( Int $) :( Num $) :( Str $)
您正在使用命名参数调用new
:( :price($) )
问题是因为你没有写一个会接受它的,它使用Mu提供的默认新.
如果您不想允许内置new,可以编写一个proto方法来阻止它搜索继承链.
proto method new (|) {*}
如果您愿意,您也可以使用它来确保所有潜在的子类也遵循关于只有一个位置参数的规则.
proto method new ($) {*}
如果要使用命名参数,请使用它们.
multi method new (Int :$price!){…}
您可能希望单独留下新的并使用多子方法BUILD.
multi submethod BUILD (Int :$!price!) { say "Int constructor"; } multi submethod BUILD (Num :$price!) { say "Num constructor"; $!price = Int($price * 100); } multi submethod BUILD (Str :$price!) { say "String constructor"; $price .= trans(/<-[0..9.]>/ => ''); unless ($price ~~ m/\.\d**2$/) { die(X::Price::PriceInvalid(:$price)); } $!price = Int($price * 100); }
实际上我总是将输入乘以100,因此1将与“1”和1/1和1e0相同.
我也会将输出除以100得到一只老鼠.
unit class Price:ver<0.0.1>; class X::Price::PriceInvalid is Exception { has $.price; method message() { return "Price $!price not valid" } } # Price is stored in cents USD has Int $.price is required; method price () { $!price / 100; # return a Rat } # Real is all Numeric values except Complex multi submethod BUILD ( Real :$price ){ $!price = Int($price * 100); } multi submethod BUILD ( Str :$price ){ $price .= trans(/<-[0..9.]>/ => ''); unless ($price ~~ m/\.\d**2$/) { X::Price::PriceInvalid(:$price).throw; } $!price = Int($price * 100); } method Str() { return sprintf("%.2f",($!price/100)); }