假设我们要创建一个自定义Probability
类型以表示0到1之间的数字。我们可以这样做:
type Probability float64
func NewProbability(p float64) (*Probability, error) {
if p < 0 || p > 1 {
return nil, errors.New("Invalid Probability")
}
tmp := Probability(p)
return &tmp, nil
}
只要我们代码的客户始终使用我们的NewProbability
构造函数,此方法就起作用。但是他们可以通过类型转换解决它:
func main() {
// works as intended
p1, _ := NewProbability(0.5)
fmt.Println(*p1)
// errors as intended
_, err := NewProbability(2)
fmt.Println(err)
// circumvents our constraints...
// creates invalid Probability
p3 := Probability(2)
fmt.Println(p3)
}
https://play.golang.org/p/xJZQhkZLi_H
2个问题:
Probability
,它将始终有效?tmp
变量,而是执行了return &Probability(p), nil
,则会收到错误cannot take the address of Probability(p)
(Try it)。为什么在使用tmp
变量时没有出现此错误?答案 0 :(得分:1)
您可以阻止创建无效的自定义类型吗?
否。
“古典” OOP语言提倡的想法是:如果防止滥用,就不会出现问题。如果用户不阅读您的文档,将会出现问题。
答案 1 :(得分:0)
1)您可以尝试将Probability构造为其中具有隐藏浮点值的结构,但是您将无法将其用作数字。另一个选择是将IsValid()方法添加到“概率”(有点类似于NaN)。
2)概率(p)是概率类型为p的副本。它是运算结果,在分配给变量之前,没有地址。将其分配给变量后,即可获取该变量的地址。
答案 2 :(得分:0)
每隔一段时间就会出现这个问题。 “确保”没有将无效值分配给某些自定义类型的唯一方法是在未导出的结构字段中使用getter和setter对其进行保护:
type Probability struct {
p float64
}
func NewProbability(p float64) (Probability, error) {
if p < 0 || p > 1 {
return Probability{}, errors.New("invalid probability")
}
return Probability{p}
}
对于像浮标这样简单的东西,这可能是肿的过度杀伤力。较明智的方法通常是在每次接受这样的参数时检查您是否收到有效的概率:
func DidItHappen(p probability) (bool, error) {
if p < 0 || p > 1 {
return false, errors.New("invalid probability")
}
if /* roll the dice */ {
return true, nil
}
return false, nil
}