Menu
Coddy logo textTech

Incrustación vs Agregación

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 38 de 107.

Has aprendido que la inclusión promueve campos y métodos a la estructura externa. Pero Go también admite la agregación, en la que incluyes otra estructura como un campo con nombre. Comprender cuándo usar cada enfoque es fundamental para un buen diseño.

Con la incrustación, omites el nombre del campo y los miembros del tipo interno se vuelven accesibles directamente:

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // embedding - sin nombre de campo
}

d := Document{}
d.Write()  // acceso directo

Con la agregación, das al campo un nombre explícito, lo que requiere ese nombre para acceder al tipo interno:

type Document struct {
    writer Writer  // agregación - campo nombrado
}

d := Document{writer: Writer{}}
d.writer.Write()  // debe usar el nombre del campo

La diferencia clave es la relación que estás expresando. La composición sugiere una relación de «es un», en la que el tipo externo adopta la identidad del tipo interno. La agregación sugiere una relación de «tiene un», en la que el tipo externo simplemente contiene el tipo interno.

Usa la incrustación cuando quieras que el tipo externo satisfaga las interfaces implementadas por el tipo interno, o cuando el acceso directo a los métodos haga que la API sea más clara. Usa la agregación cuando el tipo interno sea un detalle de implementación que no debería exponerse, o cuando quieras un control explícito sobre cómo se accede al tipo interno.

challenge icon

Desafío

Fácil

Construyamos un sistema de generación de informes que demuestre cuándo usar la incrustación frente a la agregación. Crearás un sistema en el que algunos componentes deberían ser directamente accesibles (incrustación), mientras que otros deberían permanecer como detalles de implementación internal (agregación).

Organizarás tu código en tres archivos:

  • formatter.go: Crea una estructura Formatter con un campo Style (cadena) y un método Format(text string) string que devuelve el texto envuelto con el estilo, como [Style] text [/Style]. Será un helper internal que no debería exponerse directamente.
  • report.go: Crea dos estructuras que gestionen la generación de informes de forma diferente:
    • Una estructura Metadata con los campos Author y Date. Representa information pública que debería ser directamente accesible
    • Una estructura Report con un campo Title que embeds Metadata (para el access directo al autor y la fecha), pero utiliza aggregation para Formatter (como detalle de implementación internal con un campo denominado formatter)
    Añade un método Generate() string a Report que utilice el formatter internal para dar formato al título e incluya los metadatos.
  • main.go: Lee los detalles del informe de la entrada, crea un Report con Metadata embedded y un formatter agregado. Demuestra la diferencia accediendo al autor directamente en el informe (mediante embedding) y después generando el informe formatted (que utiliza el formatter oculto internamente).

Se proporcionarán las siguientes entradas:

  • Línea 1: Título del informe
  • Línea 2: Nombre del autor
  • Línea 3: Fecha
  • Línea 4: Estilo del Formatter

Tu método Format debería devolver:

[Style] text [/Style]

Tu método Generate debería devolver:

[formatted title]
Author: [Author], Date: [Date]

En tu archivo principal, imprime tres líneas:

  1. El autor accessed directamente en Report (demostrando que embedding promueve el field)
  2. La fecha accessed mediante el nombre del tipo embedded (report.Metadata.Date)
  3. El resultado de calling Generate()

Por ejemplo, dados Quarterly Results, Jane Doe, 2024-01-15 y BOLD, tu salida debería ser:

Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15

Observa cómo Author y Date son access directamente en Report mediante embedding, mientras que el formatter permanece oculto. No puedes llamar directamente a report.Format() porque está agregado con un campo denominado formatter. Esto demuestra en la práctica la relación "is-a" frente a "has-a".

Pruébalo tú mismo

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	scanner.Scan()
	title := scanner.Text()
	
	scanner.Scan()
	author := scanner.Text()
	
	scanner.Scan()
	date := scanner.Text()
	
	scanner.Scan()
	style := scanner.Text()
	
	// TODO: Crea un struct Metadata con el author y la date
	
	// TODO: Crea un Formatter con el style dado
	
	// TODO: Crea un Report que embebe Metadata y agrega Formatter
	
	// TODO: Imprime el author accedido directamente en el Report (el embedding promueve el campo)
	
	// TODO: Imprime la date accedida a través del nombre del tipo embebido (report.Metadata.Date)
	
	// TODO: Imprime el resultado de llamar a Generate()
	
	// Suprime las advertencias de variables no usadas (elimina estas cuando uses las variables)
	_ = title
	_ = author
	_ = date
	_ = style
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos

Practica por tu cuenta: Compilador de Go online