Golang: créer un Type Constant et restreindre les valeurs du Type
j'ai une question sur les types de constantes qui sont limitées à certaines valeurs et comment vous l'accomplissez à Golang. Disons que je crée un type unary
qui a deux valeurs constantes Positive(1)
et Negative(-1)
et je veux restreindre l'utilisateur de ce type (unary
) à partir de la création d'autres valeurs de type unary
. Est-ce que j'obtiens ceci en créant un paquet et en faisant les valeurs Positive
et Negative
visible et de faire le type unary
restreint au paquet contenant? Voir code ci-dessous exemple
package unary
type unary int////not visible outside of the package unary
const (
Positive unary = 1//visible outside of the package unary
Negative unary = -1//visible outside of the package unary
)
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
}
est-ce la bonne façon de restreindre un type à certaines valeurs constantes?
2 réponses
Défauts
la solution que vous proposez n'est pas sécuritaire comme vous le souhaitez. On peut utiliser non constantes entières à créer de nouvelles valeurs de unary
différent int
valeur de 1
ou -1
. Voir cet exemple:
p := unary.Positive
fmt.Printf("%v %d\n", p, p)
p = 3
fmt.Printf("%v %d\n", p, p)
Sortie sera:
+ 1
- 3
changer p
'la valeur à stocker l' int
valeur 3
qui n'est évidemment pas égal à Positive
ni Negative
. Cela est possible parce que Spec: Cessibilité:
valeur
x
assignables variable de typeT
("x
est assignable àT
") dans l'un de ces cas:
- ...
x
est un non constant représentable par une valeur de typeT
.
3
est une constante non typée, et elle peut être représentée par une valeur de type unary
qui a le type sous-jacent int
.
dans Go vous ne pouvez pas avoir de constantes "sûres" dont les paquets "extérieurs" ne peuvent pas créer de nouvelles valeurs de, pour la raison mentionnée ci-dessus. Parce que si vous voulez déclarer constantes dans votre paquet, vous ne pouvez utiliser que des expressions qui ont des versions "non typées" - qui peuvent être utilisées par d'autres paquets aussi dans les assignations (comme dans notre exemple).
structure inexploitée
si vous voulez remplir la partie "sûr" , vous pouvez utiliser désexporter struct
s, mais alors ils ne peuvent pas être utilisés dans déclarations de constantes.
Exemple:
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
}
la Tentative de modification de sa valeur:
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
notez que puisque nous utilisons maintenant un struct
on peut simplifier notre code en ajoutant le string
représentation de nos valeurs à l' 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 }
notez que cette solution a toujours un" défaut": elle utilise des variables globales exportées, dont les valeurs peut être modifié par d'autres paquets. Il est vrai que les autres paquets ne peuvent pas créer et assigner valeurs, mais ils peuvent le faire avec les valeurs existantes, par exemple:
unary.Positive = unary.Negative
si vous voulez vous protéger d'une telle utilisation abusive, vous devez aussi faire en sorte que ces variables globales ne soient pas reproduites. Et puis bien sûr, vous devez créer des fonctions exportées afin de dénoncer ces valeurs, par exemple:
var (
positive = unary{1}
negative = unary{-1}
)
func Positive() unary { return positive }
func Negative() unary { return negative }
puis l'acquisition / l'utilisation du valeurs:
p := unary.Positive()
Interface
précautions doivent être prises si vous prévoyez d'utiliser un type d'interface pour votre "constantes". Un exemple peut être vu dans la réponse de Kaveh Shahbazian. Une méthode non utilisée est utilisée pour empêcher les autres d'implémenter l'interface, vous donnant l'illusion que les autres ne peuvent pas l'implémenter:
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 { /* ... */ }
Ce n'est pas le cas cependant. Avec un sale tour, cela peut être contourné. Le exporté Unary
type peut être intégré, et un la valeur existante peut être utilisée pour implémenter l'interface (avec la méthode non-triée), et nous pouvons ajouter nos propres implémentations des méthodes exportées, en faisant / retournant tout ce que nous voulons.
Voici à quoi ça peut ressembler à ça:
type MyUn struct {
unary.Unary
}
func (m MyUn) String() string { return "/" }
func (m MyUn) CalExpr() int { return 3 }
Test:
p := unary.Positive
fmt.Printf("%v %d\n", p, p)
p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())
Sortie:
+ 1
/ 3
cas particulier
Comme Volker a mentionné dans son commentaire, dans votre cas particulier, vous pourriez juste utilisez
type unary bool
const (
Positive unary = true
Negative unary = false
)
comme le type bool
a deux valeurs possibles: true
et false
, et nous avons tout utilisé. Donc, il n'y a pas d'autres valeurs que pourrait être "exploités" pour créer d'autres valeurs de notre constante.
mais sachez que ceci ne peut être utilisé que si le nombre de constantes est égal au nombre de valeurs possibles du type, de sorte que l'utilisabilité de cette technique est très limitée.
gardez également à l'esprit que cela n'empêche pas de tels abus lorsqu'un type de unary
est attendu, et quelqu'un dépasse accidentellement une constante non typée comme true
ou false
.
Si vous voulez juste de travailler avec int
sans introduire un type de wrapper: une façon classique de le faire dans Go est d'utiliser une interface publique avec une fonction privée; donc tout le monde peut l'utiliser mais personne ne peut l'implémenter; comme:
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
}
D'autres peuvent définir Positive
nil
cependant; mais ce N'est pas une chose dans Go world - dans de tels cas.