Introdução
Uma das coisas mais comuns em códigos escritos na linguagem Go é o uso de tipos customizados utilizando structs
. Geralmente usamos estes tipos para declarar entidades, transportar valores de forma estruturada e etc. Por exemplo, O código acima é muito comum em muitas aplicações:
type Person struct {
Name string
Document string
Email string
}
Outros tipos customizados
Mas assim como structs
, podemos utilizar outros tipos primitivos da linguagem para criar tipos customizados, abrindo assim um leque de oportunidades. O processo de criação é idêntico ao mostrado anteriormente, mas usando outro tipo primitivo como bases. Aqui vão alguns exemplos:
// Tipo customizado baseado em string
type MyCustomString string
// Baseado em int
type MyCustomInt int
// Baseado em float
type MyCustomFloat64 float64
// Baseado em boolean
type MyCustomBool bool
// Baseado em slice
type MyCustomSlice []string
// Baseado em... já deu para entender, né!?
Mas como eu uso?
Para usar esses tipos novos é tão simples quanto você está pensa. Eles operam da mesma forma que seus tipos base, assim como a struct
. Dá uma olhada:
func main() {
// Declarando variáveis com tipos personalizados
var str MyCustomString = "example"
var num MyCustomInt = 42
var flt MyCustomFloat64 = 3.14
var boolean MyCustomBool = true
var slice = MyCustomSlice{"example1", "example2"}
// Operando com as variáveis
str += " string"
num += 10
flt *= 2.0
boolean = !boolean
slice = append(slice, "example3")
// Exibindo os valores e tipos das variáveis
log.Printf("Value of str: %s, type: %T", str, str) //Value of str: example string, type: main.MyCustomString
log.Printf("Value of num: %d, type: %T", num, num) //Value of num: 52, type: main.MyCustomInt
log.Printf("Value of flt: %.2f, type: %T", flt, flt) //Value of flt: 6.28, type: main.MyCustomFloat64
log.Printf("Value of boolean: %t, type: %T", boolean, boolean) //Value of boolean: false, type: main.MyCustomBool
log.Printf("Value of boolean: %s, type: %T", slice, slice) //Value of boolean: [example1 example2 example3], type: main.MyCustomSlice
}
Legal, né!?
Adicionando métodos
Uma possibilidade legal que esta abordagem nos traz é a capacidade de as variáveis criadas a partir destes tipo chamarem métodos customizados. Isso pode ter várias aplicações interessantes. Olha só:
type MyCustomInt int
func (my MyCustomInt) Positive() bool {
return my > 0
}
func main() {
var num MyCustomInt = 42
log.Println("num Positive:", num.Positive()) // num Positive: true
}
Olhando aquele nosso exemplo inicial da struct Person
, podemos aplicar esses princípios para os campos Email
e Document
ao criar um novo tipo para cada um:
type Document string
func (d Document) Validate() bool {
return len(d) > 0
}
type Email string
func (e Email) Validate() bool {
if len(e) == 0 {
return false
}
return strings.Contains(string(e), "@") && strings.Contains(string(e), ".")
}
Assim cada tipo sabe como fazer sua própria validação. A Person
fica assim então:
type Person struct {
Name string
Document Document
Email Email
}
Olha como o uso fica legal:
person := Person{
Name: "John Doe",
Document: "123456789",
Email: "johndoe@test.com",
}
log.Printf("Person: %+v", person) // Person: {Name:John Doe Document:123456789 Email:johndoe@test.com}
log.Printf("Person Document valid: %t", person.Document.Validate()) //Person Document valid: true
log.Printf("Person Email valid: %t", person.Email.Validate()) //Person Email valid: true
Dá pra usar interfaces
Para melhorar ainda mais nosso exemplo, podemos fazer a pergunta: E se eu quiser usar o mesmo campo Document
para vários tipos de documento (digamos que RG e CPF)?
Podemos então mudar o tipo de Document
para interface
, e criar os tipos dos outros documentos que implementam esta nova interface. Melhor mostrando, né?
type Document interface {
Validate() bool
}
type DocumentRG string
type DocumentCPF string
func (d DocumentRG) Validate() bool {
log.Println("RG validation logic")
return len(d) > 0
}
func (d DocumentCPF) Validate() bool {
log.Println("CPF validation logic")
return len(d) > 0
}
Na Person
nada muda, tá?!
Mas se liga aqui como fica o uso:
person := Person{
Name: "John Doe",
Document: DocumentCPF("123456789"),
Email: "johndoe@test.com",
}
person2 := Person{
Name: "Maike Doe",
Document: DocumentRG("0987654321"),
Email: "maikedoe@test.com",
}
log.Printf("Person1: %+v", person) // Person1: {Name:John Doe Document:123456789 Email:johndoe@test.com}
log.Printf("Person1 Document valid: %t", person.Document.Validate()) // CPF validation logic // Person1 Document valid: true
log.Printf("Person2: %+v", person2) // Person2: {Name:Maike Doe Document:0987654321 Email:maikedoe@test.com}
log.Printf("Person2 Document valid: %t", person2.Document.Validate()) //RG validation logic //Person2 Document valid: true
Dessa forma, você pode ter vários tipos de documentos, e quem vai implementar é quem decide qual vai usar.
Utilizando enums
Para fecharmos, podemos fazer a pergunta: E se eu quiser ter um campo que indique o tipo de documento?
A gente pode usar Enums para isso. Espia:
type DocumentType uint
const (
DocumentTypeRG DocumentType = iota
DocumentTypeCPF
DocumentTypeCNH
DocumentTypePassaporte
)
func (d DocumentType) String() string {
switch d {
case DocumentTypeRG:
return "RG"
case DocumentTypeCPF:
return "CPF"
case DocumentTypeCNH:
return "CNH"
case DocumentTypePassaporte:
return "Passaporte"
default:
return "Unknown"
}
}
Adicionamos então o campo DocumentType
em Person
:
type Person struct {
Name string
Document Document
DocumentType DocumentType
Email Email
}
E para usar também é bem simples:
person := Person{
Name: "John Doe",
Document: DocumentCPF("123456789"),
DocumentType: DocumentTypeCPF,
Email: "johndoe@test.com",
}
person2 := Person{
Name: "Maike Doe",
Document: DocumentRG("0987654321"),
DocumentType: DocumentTypeRG,
Email: "maikedoe@test.com",
}
log.Printf("Person1: %+v", person) // Person1: {Name:John Doe Document:123456789 DocumentType:CPF Email:johndoe@test.com}
log.Println("Document Type:", person.DocumentType) // Document Type: CPF
log.Printf("Person2: %+v", person2) // Person2: {Name:Maike Doe Document:0987654321 DocumentType:RG Email:maikedoe@test.com}
log.Println("Document Type:", person2.DocumentType) // Document Type: RG
E voilà!
Vimos aqui então que podemos criar tipos customizados baseados em tipos primitivos, chamar métodos através deles, implementar interfaces e até utilizar Enums.
E aí, o que achou dessas dicas? Deixe aí nos comentários.
Obs. Cover image criada com IA.