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 incrustación (embedding) promueve campos y métodos al struct externo. Pero Go también admite la agregación, donde incluyes otro struct como un campo con nombre. Comprender cuándo usar cada enfoque es clave para un buen diseño.

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

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

type Document struct {
    Writer  // incrustación - sin nombre de campo
}

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

Con la agregación, le 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 con nombre
}

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 incrustación (embedding) sugiere una relación "es-un" (is-a) donde el tipo externo asume la identidad del tipo interno. La agregación sugiere una relación "tiene-un" (has-a) donde el tipo externo simplemente contiene al tipo interno.

Usa el embedding 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 limpia. Usa la agregación cuando el tipo interno sea un detalle de implementación que no deba ser expuesto, o cuando quieras un control explícito sobre cómo se accede al tipo interno.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de generación de informes que demuestre cuándo usar la incrustación (embedding) frente a la agregación (aggregation). Crearás un sistema donde algunos componentes deben ser directamente accesibles (incrustación) mientras que otros deben permanecer como detalles de implementación interna (agregación).

Organizarás tu código en tres archivos:

  • formatter.go: Crea una estructura Formatter con un campo Style (string) y un método Format(text string) string que devuelva el texto envuelto con el estilo, como [Style] text [/Style]. Este será un ayudante interno que no debería estar expuesto directamente.
  • report.go: Crea dos estructuras que manejen la generación de informes de manera diferente:
    • Una estructura Metadata con los campos Author y Date; esto representa información pública que debería ser directamente accesible.
    • Una estructura Report con un campo Title que incrusta (embeds) Metadata (para el acceso directo al autor y la fecha) pero utiliza agregación para Formatter (como un detalle de implementación interna con un campo con nombre formatter).
    Añade un método Generate() string a Report que utilice el formateador interno para dar formato al título e incluya los metadatos.
  • main.go: Lee los detalles del informe desde la entrada, crea un Report con metadatos incrustados y un formateador agregado. Demuestra la diferencia accediendo al autor directamente en el informe (a través de la incrustación) y luego generando el informe formateado (que utiliza internamente el formateador oculto).

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 formateador

Tu método Format debe devolver:

[Style] text [/Style]

Tu método Generate debe devolver:

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

En tu archivo principal, imprime tres líneas:

  1. El autor accedido directamente en el Report (demostrando que la incrustación promueve el campo).
  2. La fecha accedida a través del nombre del tipo incrustado (report.Metadata.Date).
  3. El resultado de llamar a 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 accesibles directamente en el Report a través de la incrustación, mientras que el formateador permanece oculto; no puedes llamar a report.Format() directamente porque está agregado con un campo con nombre. Esto demuestra la relación "es-un" (is-a) frente a "tiene-un" (has-a) en la práctica.

Hoja de referencia

Go admite dos formas de incluir un struct dentro de otro: embedding (incrustación) y aggregation (agregación).

El Embedding omite el nombre del campo, promocionando los campos y métodos del tipo interno al struct externo:

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 al método incrustado

La Aggregation utiliza un nombre de campo explícito, lo que requiere ese nombre para acceder al tipo interno:

type Document struct {
    writer Writer  // aggregation - campo con nombre
}

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

Cuándo usar cada uno:

  • Embedding: Expresa una relación "es-un" (is-a). Úselo cuando el tipo externo deba satisfacer las interfaces del tipo interno, o cuando el acceso directo a los métodos mejore la API.
  • Aggregation: Expresa una relación "tiene-un" (has-a). Úselo cuando el tipo interno sea un detalle de implementación que no deba exponerse, o cuando desee un control explícito sobre el acceso.

Con el embedding, aún puede acceder a los campos a través del nombre del tipo incrustado si es necesario:

type Metadata struct {
    Author string
}

type Report struct {
    Metadata  // embedded (incrustado)
}

r := Report{Metadata: Metadata{Author: "Jane"}}
r.Author              // acceso directo mediante promoción
r.Metadata.Author     // acceso explícito a través del nombre del tipo

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: Create a Metadata struct with the author and date
	
	// TODO: Create a Formatter with the given style
	
	// TODO: Create a Report that embeds Metadata and aggregates Formatter
	
	// TODO: Print the author accessed directly on the Report (embedding promotes the field)
	
	// TODO: Print the date accessed through the embedded type name (report.Metadata.Date)
	
	// TODO: Print the result of calling Generate()
	
	// Suppress unused variable warnings (remove these when you use the 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