programacion - Golang: Creación de un tipo de constante y restricción de los valores del tipo
golang con ejemplos (2)
Tengo una pregunta sobre tipos de constantes que están restringidas a ciertos valores y cómo lo logras en Golang. Digamos que creo un tipo unary
que tiene dos valores constantes Positive(1)
y Negative(-1)
y quiero restringir al usuario de ese tipo ( unary
) para que no cree otros valores de tipo unary
. ¿Logro esto creando un paquete y haciendo que los valores Positive
y Negative
visibles y haciendo que el tipo unary
restrinja al paquete que lo contiene? Ver el código a continuación, por ejemplo
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
}
¿Es esta la forma correcta de restringir un tipo a ciertos valores constantes?
Si le gusta simplemente trabajar con int
sin introducir un tipo de contenedor: una forma clásica de hacerlo en Go es usar una interfaz pública con una función privada; para que todos puedan usarlo pero nadie puede implementarlo; me gusta:
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
}
Otros pueden establecer Positive
a nil
; pero eso no es algo en el mundo Go, en tales casos.
Como mencionó @icza, uno puede sobrescribir los métodos públicos. Pero para los métodos privados, Go no llamará "el más superficial", sino que llamará al original.
Defectos
La solución propuesta no es segura de la manera que usted desea. Se pueden usar constantes de enteros sin tipo para crear nuevos valores de unary
con un valor int
diferente de 1
o -1
. Mira este ejemplo:
p := unary.Positive
fmt.Printf("%v %d/n", p, p)
p = 3
fmt.Printf("%v %d/n", p, p)
La salida será:
+ 1
- 3
Podríamos cambiar el valor de p
para almacenar el valor int
3
que obviamente no es igual a Positive
ni a Negative
. Esto es posible porque Spec: Asignabilidad:
Un valor
x
se puede asignar a una variable de tipoT
("x
se puede asignar aT
") en cualquiera de estos casos:
- ...
x
es una constante sin tipo representable por un valor de tipoT
3
es una constante sin tipo, y es representable por un valor de tipo unary
que tiene el tipo subyacente int
.
En Go no puede haber constantes "seguras" de las cuales los paquetes "outsider" no pueden crear nuevos valores de, por la razón mencionada anteriormente. Porque si desea declarar constantes en su paquete, solo puede usar expresiones que tengan versiones "sin tipo", que también pueden ser utilizadas por otros paquetes en asignaciones (como en nuestro ejemplo).
Estructura no exportada
Si desea cumplir con la parte "segura", puede usar struct
expuestas, pero no pueden usarse en declaraciones constantes .
Ejemplo:
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
}
Intentando cambiar su valor:
p := unary.Positive
p.val = 3 // Error: p.val undefied (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
Tenga en cuenta que dado que ahora estamos usando una struct
, podemos simplificar aún más nuestro código agregando la representación de string
de nuestros valores a la 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 }
Tenga en cuenta que esta solución todavía tiene un "defecto": utiliza variables globales exportadas, cuyos valores pueden ser modificados por otros paquetes. Es cierto que otros paquetes no pueden crear y asignar nuevos valores, pero pueden hacerlo con valores existentes, por ejemplo:
unary.Positive = unary.Negative
Si desea protegerse de dicho uso indebido, también debe hacer que las variables globales no se publiquen. Y luego, por supuesto, debe crear funciones exportadas para exponer esos valores, por ejemplo:
var (
positive = unary{1}
negative = unary{-1}
)
func Positive() unary { return positive }
func Negative() unary { return negative }
Luego, adquirir / usar los valores:
p := unary.Positive()
Interfaz
Se debe tener cuidado si planea usar un tipo de interfaz para sus "constantes". Un ejemplo se puede ver en la respuesta de Kaveh Shahbazian. Se usa un método no exportado para evitar que otros implementen la interfaz, lo que le da la ilusión de que otros realmente no pueden implementarlo:
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 { /* ... */ }
Este no es el caso, sin embargo. Con un truco sucio, esto puede ser evitado. El tipo Unary
exportado se puede incrustar, y se puede usar un valor existente para implementar la interfaz (junto con el método no exportado), y podemos agregar nuestras propias implementaciones de los métodos exportados, haciendo / devolviendo lo que queramos.
Así es como puede verse:
type MyUn struct {
unary.Unary
}
func (m MyUn) String() string { return "/" }
func (m MyUn) CalExpr() int { return 3 }
Probándolo:
p := unary.Positive
fmt.Printf("%v %d/n", p, p)
p = MyUn{p}
fmt.Printf("%v %d/n", p, p.CalExpr())
Salida:
+ 1
/ 3
Caso especial
Como Volker mencionó en su comentario, en su caso especial podría usar
type unary bool
const (
Positive unary = true
Negative unary = false
)
Como el tipo bool
tiene dos valores posibles: true
y false
, y lo hemos usado todo. Entonces, no hay otros valores que puedan ser "explotados" para crear otros valores de nuestro tipo constante.
Pero sepa que esto solo se puede usar si el número de constantes es igual al número de valores posibles del tipo, por lo que la usabilidad de esta técnica es muy limitada.
También tenga en cuenta que esto no evita tales usos indebidos cuando se espera un tipo de unary
, y alguien accidentalmente pasa una constante sin tipo como true
o false
.