32 - Campos anónimos en un struct

El lenguaje Go no es orientado a objetos puro, pero define varios de sus principios.
En el concepto anterior vimos que a un struct se le pueden asociar métodos que manipulan sus campos (concepto de encapsulamiento), ahora veremos que un struct puede definir campos que heredan de otro struct todos sus campos y métodos.

Esto se puede comparar con el concepto de herencia de los lenguajes orientados a objetos.

Veremos con un ejemplo como se define un campo anónimo.

Programa: ejercicio140.go

package main

import "fmt"

type Persona struct {
    nombre string
    edad int
}

func (per *Persona) cargar() {
    fmt.Print("Ingrese el nombre:")
    fmt.Scan(&per.nombre)
    fmt.Print("Ingrese la edad:")
    fmt.Scan(&per.edad)
}

func (per Persona) imprimir() {
    fmt.Println("Datos Personales")
    fmt.Println("***** **********")
    fmt.Println("Nombre:", per.nombre)
    fmt.Println("Edad:", per.edad)
}

func (per Persona) mayorEdad() {
    if per.edad >= 18 {
        fmt.Println(per.nombre, "es mayor de edad")
    } else {
        fmt.Println(per.nombre, "no es mayor de edad")
    }
}

type Empleado struct {
    Persona
    sueldo int
}

func (emp *Empleado) cargar() {
    fmt.Println("Ingreso de datos del empleado")
    emp.Persona.cargar()
    fmt.Print("Ingrese el sueldo:")
    fmt.Scan(&emp.sueldo)
}

func (emp Empleado) imprimir() {
    emp.Persona.imprimir()
    fmt.Println("Sueldo:", emp.sueldo)
}

func (emp Empleado) pagaImpuestos() {
    if emp.sueldo >= 300 {
        fmt.Println(emp.nombre, "debe pagar impuestos")
    } else {
        fmt.Println(emp.nombre, "no debe pagar impuestos")
    }
}


func main() {
    var persona1 Persona
    persona1.cargar()
    persona1.imprimir()
    persona1.mayorEdad()
    var empleado1 Empleado
    empleado1.cargar()
    empleado1.imprimir()
    empleado1.pagaImpuestos()
} 

El problema representa dos conceptos, por un lado tenemos declarado el struct Persona y una serie de métodos asociados a este struct que nos permiten cargar su nombre y edad, imprimir los datos y mostrar un mensaje si es mayor de edad:

type Persona struct {
    nombre string
    edad int
}

func (per *Persona) cargar() {
    fmt.Print("Ingrese el nombre:")
    fmt.Scan(&per.nombre)
    fmt.Print("Ingrese la edad:")
    fmt.Scan(&per.edad)
}

func (per Persona) imprimir() {
    fmt.Println("Datos Personales")
    fmt.Println("***** **********")
    fmt.Println("Nombre:", per.nombre)
    fmt.Println("Edad:", per.edad)
}

func (per Persona) mayorEdad() {
    if per.edad >= 18 {
        fmt.Println(per.nombre, "es mayor de edad")
    } else {
        fmt.Println(per.nombre, "no es mayor de edad")
    }
}

En la main definimos un struct de tipo Persona y llamamos a sus métodos:

func main() {
    var persona1 Persona
    persona1.cargar()
    persona1.imprimir()
    persona1.mayorEdad()

Este problema lo vimos en el concepto anterior al presentar el tema de métodos.

Lo nuevo aparece cuando queremos representar el concepto de Empleado y queremos definir como atributos su nombre, edad y sueldo. Como vemos un Empleado es una Persona que añade la característica que tiene sueldo. Mediante la definición de un campo anónimo en Go podemos añadir todos los campos y métodos asociados con la sintaxis:

type Empleado struct {
    Persona
    sueldo int
}

Hemos definido el campo anónimo llamado Persona.

Ahora cuando definamos una variable de tipo Empleado estamos reservando espacio para todos los campos definidos en Empleado y Persona (es decir 3 campos), pero además tenemos acceso a todos los métodos asociados al struct Persona.

El struct Empleado también define sus propios métodos:

func (emp *Empleado) cargar() {
    fmt.Println("Ingreso de datos del empleado")
    emp.Persona.cargar()
    fmt.Print("Ingrese el sueldo:")
    fmt.Scan(&emp.sueldo)
}

El método cargar llama al método cargar del campo anónimo Persona: emp.Persona.cargar()

El método cargar del struct Empleado extiende el método cargar del struct Persona cargando el atributo sueldo.

De forma similar para imprimir los datos del Empleado llamamos al imprimir de Persona:

func (emp Empleado) imprimir() {
    emp.Persona.imprimir()
    fmt.Println("Sueldo:", emp.sueldo)
}

En el método pagarImpuestos podemos acceder al campo "nombre" sin la necesidad de anteceder el nombre del campo anónimo:

func (emp Empleado) pagaImpuestos() {
    if emp.sueldo >= 300 {
        fmt.Println(emp.nombre, "debe pagar impuestos")
    } else {
        fmt.Println(emp.nombre, "no debe pagar impuestos")
    }
}

Podemos pero no es necesario indicar que nombre pertenece al campo anónimo:

        fmt.Println(emp.Persona.nombre, "debe pagar impuestos")

En la main definimos una variable de tipo Empleado y llamamos a sus métodos:

    var empleado1 Empleado
    empleado1.cargar()
    empleado1.imprimir()
    empleado1.pagaImpuestos()

Problema propuesto

  • Se tienen las siguientes declaraciones:

    type Usuario struct {
        nombre string
        clave string
    }
    
    type Administrador struct {
        Usuario
        privilegio int
    }
    
    Definir los siguientes métodos para el struct Usuario:
    func (usu *Usuario) cargar(nombre, clave string) {
    
    func (usu Usuario) imprimir() {
    
    y para el administrador:
    func (admin *Administrador) cargar(nombre, clave string, privilegio int) {
    
    func (admin Administrador) imprimirPrivilegio() {
    
    En la función main definir solo una variable de tipo Adminstrador:
    func main() {
        var administrador1 Administrador
        administrador1.cargar("juan", "abc123", 1)
        administrador1.imprimirPrivilegio()
    }
    
Solución
ejercicio141.go

package main

import "fmt"

type Usuario struct {
    nombre string
    clave string
}

type Administrador struct {
    Usuario
    privilegio int
}


func (usu *Usuario) cargar(nombre, clave string) {
    usu.nombre = nombre
    usu.clave = clave
}

func (usu Usuario) imprimir() {
    fmt.Println("Nombre:", usu.nombre)
    fmt.Println("Clave:", usu.clave)
}

func (admin *Administrador) cargar(nombre, clave string, privilegio int) {
    admin.Usuario.cargar(nombre, clave)
    admin.privilegio = privilegio
}

func (admin Administrador) imprimirPrivilegio() {
    admin.imprimir()
    fmt.Println("Privilegio: ", admin.privilegio)
}


func main() {
    var administrador1 Administrador
    administrador1.cargar("juan", "abc123", 1)
    administrador1.imprimirPrivilegio()
}