Golang:创建常量类型并限制类型的值

时间:2016-05-23 07:29:58

标签: go constants

我有一个关于限制为某些值的常量类型以及如何在Golang中实现它的问题。假设我创建了一个unary类型,它有两个常量值Positive(1)Negative(-1),我想限制该类型的用户(unary)创建其他类型的值{ {1}}。我是否通过创建包并使值unaryPositive可见并使类型Negative限制为包含包来实现此目的?请参阅下面的代码,例如

unary

这是将类型限制为某些常量值的正确方法吗?

2 个答案:

答案 0 :(得分:30)

缺陷

您提出的解决方案并不是您想要的安全方式。可以使用无类型整数常量来创建unary的新值,其int值不同于1-1。见这个例子:

p := unary.Positive
fmt.Printf("%v %d\n", p, p)

p = 3
fmt.Printf("%v %d\n", p, p)

输出将是:

+ 1
- 3

我们可以更改p的值,以存储int3,这显然不等于Positive,也不会Negative。这是可能的,因为Spec: Assignability:

  

x 可分配T类型的variable(“x可分配给T”)在任何这些情况下:

     
      
  • ...
  •   
  • x是一种无类型的constant,可以使用T类型的值表示。
  •   

3是一个无类型常量,它可以由unary类型的值表示,其类型为int

在Go中,由于上述原因,您不能拥有“安全”常量,其中“局外人”包不能创建新值。因为如果要在包中声明常量,则只能使用具有“无类型”版本的表达式 - 其他包也可以在赋值中使用(就像在我们的示例中一样)。

Unexported struct

如果您想要完成“安全”部分,可以使用未导出的struct,但不能在constant declarations中使用它们。

示例:

type unary struct {
    val int
}

var (
    Positive = unary{1}
    Negative = unary{-1}
)

func (u unary) String() string {
    if u == Positive {
        return "+"
    }
    return "-"
}

func (u unary) CalExpr() int {
    return u.val
}

试图改变其价值:

p := unary.Positive

p.val = 3 // Error: p.val undefined (cannot refer to unexported field or method val)

p = unary.unary{3} // Error: cannot refer to unexported name unary.unary
// Also error: implicit assignment of unexported field 'val' in unary.unary literal

请注意,由于我们现在使用的是struct,因此我们可以通过将string值的struct表示添加到type unary struct { val int str string } var ( Positive = unary{1, "+"} Negative = unary{-1, "-"} ) func (u unary) String() string { return u.str } func (u unary) CalExpr() int { return u.val } 来进一步简化我们的代码:

unary.Positive = unary.Negative

请注意,此解决方案仍然存在“缺陷”:它使用导出的全局变量,其值可以由其他包更改。确实,其他软件包无法创建和分配 new 值,但它们可以使用现有值来实现,例如:

var (
    positive = unary{1}
    negative = unary{-1}
)

func Positive() unary { return positive }

func Negative() unary { return negative }

如果你想保护自己免受这种滥用,你还必须使这些全局变量不被移植。当然,您必须创建导出的函数以公开这些值,例如:

p := unary.Positive()

然后获取/使用值:

type Unary interface {
    fmt.Stringer
    CalExpr() int
    disabler() // implementing this interface outside this package is disabled
}

var (
    Positive Unary = unary(1)  // visible outside of the package unary
    Negative Unary = unary(-1) // visible outside of the package unary
)

type unary int // not visible outside of the package unary

func (u unary) disabler() {}

func (u unary) String() string { /* ... */ }

func (u unary) CalExpr() int { /* ... */ }

接口

如果您计划为“常量”使用接口类型,则必须小心。在Kaveh Shahbazian的回答中可以看到一个例子。一个未导出的方法用于防止其他人实现该接口,给你一种其他人真正无法实现它的错觉:

Unary

但事实并非如此。有了肮脏的把戏,这可以被规避。可以嵌入导出的type MyUn struct { unary.Unary } func (m MyUn) String() string { return "/" } func (m MyUn) CalExpr() int { return 3 } 类型,并且可以使用现有值来实现接口(以及未导出的方法),并且我们可以添加我们自己的导出方法的实现,执行/返回我们的任何内容想要。

以下是它的样子:

p := unary.Positive
fmt.Printf("%v %d\n", p, p)

p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())

测试它:

+ 1
/ 3

输出:

type unary bool

const (
    Positive unary = true
    Negative unary = false
)

特例

正如沃尔克在评论中提到的那样,在你的特殊情况下你可以使用

bool

由于类型true有两个可能的值:falseunary,我们使用了全部。因此,没有其他值可以“利用”来创建常量类型的其他值。

但是要知道只有当常数的数量等于该类型的可能值的数量时才能使用,所以这种技术的可用性非常有限。

另请注意,如果需要true类型,并且有人意外地传递了falselayout-column这样的无类型常量,则不会阻止此类误操作。

答案 1 :(得分:1)

如果您想在不引入包装类型的情况下使用int:在Go中执行此操作的经典方法是使用具有私有函数的公共接口;所以每个人都可以使用它,但没有人可以实现它;像:

type Unary interface {
    fmt.Stringer
    CalExpr() int
    disabler() //implementing this interface outside this package is disabled
}

var (
    Positive Unary = unary(1)  //visible outside of the package unary
    Negative Unary = unary(-1) //visible outside of the package unary
)

type unary int //not visible outside of the package unary

func (u unary) disabler() {}

func (u unary) String() string { //visible outside of the package unary
    if u == Positive {
        return "+"
    }
    return "-"
}

func (u unary) CalExpr() int { //visible outside of the package unary
    if u == Positive {
        return 1
    }
    return -1
}

其他人可以将Positive设置为nil;但在这种情况下,这不是Go世界的事情。

正如@icza所提到的,可以覆盖公共方法。但对于私人方法,Go不会打电话给#34;最浅的"一个,而是调用原始。