【问题标题】:Using automatically generated methods for public attributes vs creating methods for accessing private attributes使用自动生成的公共属性方法与创建访问私有属性的方法
【发布时间】:2020-01-02 18:13:51
【问题描述】:

假设我有一个具有多个属性的类,但我只需要其中一些属性来构建对象;其余属性的值取决于这些 public 属性。但是,我仍然想使用以属性命名的方法来访问其余属性的值。在我的脑海中,有两个选择:

第一个选项:

  • 重载new 方法。或者保持new 方法不变,而是使用BUILD 子方法将传递的参数设置为正确的属性,并使用此参数的值设置剩余的属性。
class Foo {
    has Int $.a;
    has Int $.b;
    has Int $.c;

    submethod BUILD(:$!a) {
        $!b = $!a ** 2;
        $!c = $!a ** 3;
    }
}
say Foo.new(:2a, :1b);   #=> Foo.new(a => 2, b => 4, c => 8)
say Foo.new(:2a, :1b).b; #=> 8

第二个选项:

仅使用$. 声明对象构造所需的属性,并在对象构造后使用TWEAK 子方法修改其余属性(使用$! 声明)。但是,现在我需要为这些属性创建访问器方法。

class Bar {
    has Int $.a;
    has Int $!b;
    has Int $!c;

    submethod TWEAK {
        $!b = $!a ** 2;
        $!c = $!a ** 3;
    }

    method b { $!b }
    method c { $!c }
}
say Bar.new(:2a, :1b);   #=> Bar.new(a => 2)
say Bar.new(:2a, :1b).b; #=> 8

问题

最好的选项是什么?还有其他选择吗?每个选项的优点/缺点是什么?

【问题讨论】:

  • 考虑到传递bc 参数对对象创建没有影响,我觉得有些奇怪,有人会发现Foo.new(a => 2, b => 4, c => 8).perl 输出是可以接受的。
  • 也许属性上的“可访问”特征会自动对私有属性执行选项 2?我去过那个地方很多次了。
  • 在解决问题的仓库中添加了一个问题:github.com/perl6/problem-solving/issues/145
  • 已实现并放入 PR:github.com/rakudo/rakudo/pull/3404
  • 它现在作为is built 在核心中:$!a is built 现在允许您使用.new 设置私有访问器,但无需为其创建访问器。 $.a is built(False) 将为属性创建一个访问器,但会禁止使用.new 设置它。

标签: class oop attributes raku


【解决方案1】:

就我个人而言,我会做得又好又简单:

class Foo { 
    has Int $.a; 
    has Int $!b = $!a ** 2; 
    has Int $!c = $!a ** 3;
    method b { $!b }
    method c { $!c }
}
say Foo.new(:2a, :1b); #=>  Foo.new(a => 2)
say Foo.new(:2a, :1b).b; #=> 4

只需使用属性的默认构造函数和默认值,并添加几个基本的读取方法。默认构造函数仅更新公共属性,因此如果您尝试覆盖 bc,则会被忽略。

如果您愿意,可以在有人尝试设置错误时添加BUILD 子方法。

在 cmets 中针对这种情况(也可能是最终用例)提出了一个很好的观点:

class Foo { 
    has Int $.a; 
    method b() is pure { $!a ** 2 }
    method c() is pure { $!a ** 3 }
}
say Foo.new(:2a, :1b); #=>  Foo.new(a => 2)
say Foo.new(:2a, :1b).b; #=> 4

根据计算的复杂性,您可能还想使用 is cached 特征。

【讨论】:

  • 我什至可能会选择class Foo { has Int $.a; method b { $!a ** 2 }; method c { $!a ** 3 } },它更简单,即使它在技术上增加了一些开销(虽然如果只是一个简单的数学运算,则很小,如果它是主要的,is cached 可以添加到加快速度)。
  • 我不介意这个答案。我唯一要补充的是,如果 $!a 是构造对象所必需的,那么 IMO 应该使用 is required 特征。
【解决方案2】:

几周前我读到一篇文章,谈到了类的构造函数参数与其公共接口之间的这种歧义,但我再也找不到它了。

但我认为您可以利用 FALLBACK

class Bar {
    has Int $.a;
    has Int $!b;
    has Int $!c;

    submethod TWEAK {
        $!b = $!a ** 2;
        $!c = $!a ** 3;
    }

    method FALLBACK( $name ) {
        self.^attributes.first( *.name eq "\$!$name" ).?get_value(self);
    }
}

say Bar.new(:2a);
say Bar.new(:2a).c;

虽然这有点 hacky,而且成本很高,因为属性查找必须通过 FALLBACK 和自省。拥有一个可以创建访问器但对构造函数没有影响的特征会很好。类似的东西

class Bar {
    has Int $.a;
    has Int $.b is shady; # or whatever name
    has Int $.c is shady;
}

但我不知道有这样的事情存在。

【讨论】:

  • 我很想阅读您提到的文章。我想我会避免使用FALLBACK,因为促使我提出这个问题的课程非常简单,因此在这种情况下使用元编程会有点过分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
  • 2013-10-28
  • 1970-01-01
  • 2020-12-20
  • 2022-11-22
  • 2012-03-20
  • 2018-03-28
相关资源
最近更新 更多