Skip to content
SP StackPractices
beginner Por StackPractices

Patrón Partial Class

Divide la definición de una única clase en múltiples archivos fuente para separar código auto-generado de código escrito a mano, o para organizar clases grandes por concern.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Patrón Partial Class

Descripción General

El Patrón Partial Class divide la definición de una única clase en múltiples archivos fuente. En tiempo de compilación, los fragmentos se fusionan en un único tipo. Esta separación permite que el código auto-generado (de designers, ORMs o generadores de código) viva en un archivo mientras las customizaciones escritas a mano viven en otro, sin riesgo de que uno sobrescriba al otro.

Aunque más asociado con C# (partial class), el concepto existe en otras formas: Ruby permite reabrir clases, Python permite monkey-patching, y los métodos default de interfaces de Java más records logran objetivos similares. El beneficio core es separación organizacional sin overhead en runtime.

Cuándo Usar

Usa el Patrón Partial Class cuando:

  • Los generadores de código producen boilerplate grande que no debería editarse manualmente
  • La lógica de negocio escrita a mano debería mantenerse separada del código scaffolded
  • Múltiples desarrolladores trabajan en diferentes aspectos de la misma clase simultáneamente
  • Quieres organizar una clase grande por concern (persistencia, validación, serialización)

Cuándo Evitar

  • La clase es lo suficientemente pequeña para caber cómodamente en un único archivo
  • Los splits crean dependencias circulares o navegación confusa
  • El lenguaje no soporta tipos parciales nativamente (workarounds agregan complejidad)
  • El patrón se usa para esconder que una clase creció demasiado (refactorizar en su lugar)

Solución

C# (Nativo)

// AutoGenerated.cs — generado por herramienta, no editar
public partial class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// Customer.Custom.cs — lógica de negocio escrita a mano
public partial class Customer
{
    public bool IsValidEmail()
    {
        return Email?.Contains("@") ?? false;
    }

    public string GetDisplayName()
    {
        return $"{Name} <{Email}>";
    }
}

Python

Python no tiene partial classes, pero las clases pueden reabrirse y monkey-patchearse:

from dataclasses import dataclass

# Archivo: customer_base.py (auto-generado)
@dataclass
class Customer:
    id: int
    name: str
    email: str


# Archivo: customer_custom.py (escrito a mano)
def is_valid_email(self) -> bool:
    return "@" in self.email if self.email else False

def get_display_name(self) -> str:
    return f"{self.name} <{self.email}>"

# Reabrir la clase adjuntando métodos
Customer.is_valid_email = is_valid_email
Customer.get_display_name = get_display_name


# Uso
customer = Customer(id=1, name="Alice", email="alice@example.com")
print(customer.is_valid_email())       # True
print(customer.get_display_name())    # Alice <alice@example.com>

Java

Java no tiene partial classes, pero nested classes, métodos default de interfaces y records con builders logran separación similar:

// Base auto-generado
public class CustomerBase {
    private final int id;
    private final String name;
    private final String email;

    public CustomerBase(int id, String name, String email) {
        this.id = id; this.name = name; this.email = email;
    }

    public int getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
}

// Extensión escrita a mano vía herencia
public class Customer extends CustomerBase {
    public Customer(int id, String name, String email) {
        super(id, name, email);
    }

    public boolean isValidEmail() {
        return getEmail() != null && getEmail().contains("@");
    }

    public String getDisplayName() {
        return getName() + " <" + getEmail() + ">";
    }
}

// Uso
Customer customer = new Customer(1, "Alice", "alice@example.com");
System.out.println(customer.isValidEmail());
System.out.println(customer.getDisplayName());

JavaScript

JavaScript permite extender prototypes en cualquier momento, logrando comportamiento de partial class:

// Archivo: customerBase.js (auto-generado)
class Customer {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
  }
}

// Archivo: customerCustom.js (escrito a mano)
Customer.prototype.isValidEmail = function () {
  return this.email?.includes('@') ?? false;
};

Customer.prototype.getDisplayName = function () {
  return `${this.name} <${this.email}>`;
};

// Uso
const customer = new Customer(1, 'Alice', 'alice@example.com');
console.log(customer.isValidEmail());    // true
console.log(customer.getDisplayName());  // Alice <alice@example.com>

Explicación

El Patrón Partial Class resuelve un problema de tooling y mantenimiento:

  • Antes: Los generadores de código sobrescriben cambios escritos a mano cada vez que corren
  • Después: El código generado vive en un archivo, el código custom en otro; ambos compilan a un único tipo

Esto no es sobre comportamiento en runtime (no hay diferencia runtime entre una partial y una monolítica), sino sobre organización de código fuente y seguridad de generadores.

Variantes

VarianteLenguajeMecanismo
Partial classC#, VB.NETKeyword partial nativo
Reopen classRuby, PythonMonkey-patch / reabrir en runtime
Mixin modulesRuby, PythonIncluir módulos en clase
Default interface methodsJavaMétodos de interface con cuerpo
Partial methodC#Stub generado, implementación opcional

Mejores Prácticas

  • Nunca edites archivos generados. Márcalos con comentarios // <auto-generated>.
  • Usa naming consistente. Customer.generated.cs y Customer.custom.cs hacen el intento claro.
  • Mantén partials coherentes. Separa por concern (serialización, validación), no arbitrariamente.
  • Documenta qué archivo contiene qué. Un comentario a nivel de clase ayuda la navegación.
  • Regenera en CI. Asegura que los archivos generados siempre estén frescos y no modificados manualmente.

Errores Comunes

  • Splitting arbitrario. Cinco archivos partial para una clase de 100 líneas es excesivo.
  • Crear dependencias circulares. Los partials no deberían depender unos de otros de formas confusas.
  • Mezclar código generado y escrito a mano en el mismo archivo. Derrota el propósito.
  • Usar partials para evitar refactorizar. Si una clase necesita diez partials, puede necesitar extracción en clases más pequeñas.
  • Asumir thread safety del splitting. Las partial classes tienen la misma semántica de threading que las clases regulares.

Ejemplos del Mundo Real

WinForms / WPF Designers (C#)

El designer de Windows Forms de Visual Studio genera Form1.Designer.cs con inicialización de controles auto-generada, mientras que Form1.cs contiene handlers de eventos y lógica de negocio.

Entity Framework (C#)

Las herramientas de scaffolding de EF generan partial entity classes, permitiendo a los desarrolladores agregar validación, propiedades computadas y lógica de negocio en archivos partial separados que sobreviven re-scaffolding.

ASP.NET Core Razor

Las Razor pages compilan markup (.cshtml) y code-behind (.cshtml.cs) en una única partial class, separando presentación de lógica.

Preguntas Frecuentes

Q: Cuál es la diferencia entre Partial Class e Herencia? A: Las partial classes se fusionan en tiempo de compilación en un único tipo. La herencia crea una relación runtime entre dos tipos distintos. Las partial classes no pueden agregar campos que otros partials dependan en inicialización.

Q: Las partial classes pueden tener diferentes modificadores de acceso? A: No, todas las declaraciones partial deben tener el mismo modificador de acceso y deben estar en el mismo assembly/namespace.

Q: Cómo logro partial classes en Java o Python? A: Java usa composición o herencia; Python usa monkey-patching o mixins. Ninguno tiene partials en tiempo de compilación como C#.

Q: Pueden existir métodos partial sin implementación? A: En C#, sí. Una declaración de método partial puede existir en el archivo generado sin implementación en el archivo custom. El compilador remueve el sitio de llamada por completo si no existe implementación, produciendo cero overhead.