Encapsulación, Herencia y Polimorfismo en C#
Aprende encapsulación, herencia y polimorfismo en C# con ejemplos claros para dominar los principios básicos de la POO.
La Programación Orientada a Objetos (POO / OOP) es un enfoque desarrollado para escribir código reutilizable, mantenible y comprensible. Los tres pilares fundamentales de la POO son:
- Encapsulación → Ocultar los datos y proporcionar acceso controlado
- Herencia → Transmitir comportamientos comunes a las subclases
- Polimorfismo → El mismo método se comporta de manera diferente según el objeto
Cuando estas tres características se utilizan juntas, se puede construir una arquitectura sólida en los proyectos de software. Veamos cada una de ellas por separado.
Encapsulación
La encapsulación oculta la estructura interna de una clase al mundo exterior, exponiendo solo lo necesario.
De este modo se evita la manipulación directa de los datos, se reducen los errores y los cambios en la lógica interna no afectan al exterior.
En C#, se implementa mediante modificadores de acceso como private, protected, internal y public.
Normalmente, los campos se ocultan y el acceso controlado se realiza a través de propiedades o métodos.
public class BankAccount
{
private decimal _balance; // acceso directo restringido
public decimal Balance
{
get => _balance;
private set => _balance = value;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("La cantidad debe ser mayor que cero.");
Balance += amount;
}
public void Withdraw(decimal amount)
{
if (amount > Balance)
throw new InvalidOperationException("Fondos insuficientes.");
Balance -= amount;
}
}
En este ejemplo, el saldo (_balance) no puede modificarse directamente.
El usuario solo puede interactuar con él a través de los métodos Deposit y Withdraw.
Esto evita un uso indebido y garantiza la seguridad de los datos.
Herencia
La herencia permite que una clase herede propiedades y comportamientos de otra clase. De esta forma, el código común se centraliza y se vuelve reutilizable. La clase base define los comportamientos generales, mientras que las clases derivadas agregan comportamientos especializados.
public class Animal
{
public string Name { get; set; } = string.Empty;
public void Eat()
{
Console.WriteLine($"{Name} está comiendo.");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name} está ladrando.");
}
}
public class Cat : Animal
{
public void Meow()
{
Console.WriteLine($"{Name} está maullando.");
}
}
class Program
{
static void Main()
{
Dog d = new Dog { Name = "Rex" };
d.Eat(); // heredado de Animal
d.Bark(); // específico de Dog
Cat c = new Cat { Name = "Misu" };
c.Eat(); // heredado de Animal
c.Meow(); // específico de Cat
}
}
Aquí, tanto Dog como Cat heredan de Animal.
El comportamiento común Eat() se escribe una sola vez y está disponible en todas las subclases.
Polimorfismo
El polimorfismo significa que el mismo método puede comportarse de manera diferente según el objeto. Existen dos tipos de polimorfismo:
- Polimorfismo en tiempo de compilación: Se logra con la sobrecarga de métodos (method overloading).
- Polimorfismo en tiempo de ejecución: Se logra con métodos
virtualyoverride.
public class Animal
{
public string Name { get; set; } = string.Empty;
public virtual void Speak()
{
Console.WriteLine($"{Name} hace un sonido.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine($"{Name} ladra.");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine($"{Name} maúlla.");
}
}
class Program
{
static void Main()
{
Animal a1 = new Dog { Name = "Rex" };
Animal a2 = new Cat { Name = "Misu" };
a1.Speak(); // Rex ladra.
a2.Speak(); // Misu maúlla.
}
}
La misma llamada a Speak() produce una salida diferente según el tipo de objeto.
Esta flexibilidad es una de las características más poderosas de la POO.
Ejemplo: Tipos de libros
Modelemos libros en una aplicación de biblioteca.
Las propiedades comunes se definen en la clase Book, mientras que cada subclase agrega su propia información o
redefine el método PrintInfo().
Además, las clases Author y Publisher se utilizan para almacenar la información del autor y del editor en objetos separados.
Esto demuestra cómo se pueden combinar la herencia y el polimorfismo.
public class Author
{
public string Name { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
}
public class Publisher
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
}
public abstract class Book
{
public string Title { get; set; } = string.Empty;
public Author BookAuthor { get; set; } = new Author();
public Publisher BookPublisher { get; set; } = new Publisher();
public int Pages { get; set; }
public string ISBN { get; set; } = string.Empty;
public int Year { get; set; }
// Se personalizará en las subclases
public abstract void PrintInfo();
}
public class ScienceBook : Book
{
public string Field { get; set; } = string.Empty; // Física, Química, Biología
public override void PrintInfo()
{
Console.WriteLine($"[Libro de Ciencia] {Title} - {BookAuthor.Name}, {Pages} páginas");
Console.WriteLine($"Campo: {Field}, ISBN: {ISBN}, Editorial: {BookPublisher.Name}");
}
}
public class HistoryBook : Book
{
public string Period { get; set; } = string.Empty; // Antigua, Medieval, etc.
public override void PrintInfo()
{
Console.WriteLine($"[Libro de Historia] {Title} - {BookAuthor.Name}, {Pages} páginas");
Console.WriteLine($"Período: {Period}, ISBN: {ISBN}, Editorial: {BookPublisher.Name}");
}
}
class Program
{
static void Main()
{
var publisher = new Publisher { Name = "Editorial Conocimiento", Address = "Madrid" };
var author1 = new Author { Name = "Albert Einstein", Country = "Alemania" };
var author2 = new Author { Name = "Halil Inalcik", Country = "Turquía" };
Book b1 = new ScienceBook
{
Title = "Teoría de la Relatividad",
BookAuthor = author1,
BookPublisher = publisher,
Pages = 250,
ISBN = "123-456-789",
Year = 1920,
Field = "Física"
};
Book b2 = new HistoryBook
{
Title = "El Imperio Otomano",
BookAuthor = author2,
BookPublisher = publisher,
Pages = 500,
ISBN = "987-654-321",
Year = 1973,
Period = "Época Otomana"
};
b1.PrintInfo();
Console.WriteLine();
b2.PrintInfo();
}
}
Ventajas
- Encapsulación: Proporciona seguridad de los datos y evita un uso indebido.
- Herencia: Reduce la duplicación de código y establece una estructura jerárquica.
- Polimorfismo: Permite crear una arquitectura flexible y extensible.
TL;DR
- Encapsulación: Oculta los datos internos y proporciona acceso controlado.
- Herencia: Las propiedades y comportamientos de la clase base se transmiten a las subclases.
- Polimorfismo: El mismo método puede comportarse de manera diferente en distintas subclases.
Artículos relacionados
Clases, Objetos, Propiedades y Métodos en C#
Aprende cómo las clases, objetos, propiedades y métodos en C# forman la base de la programación orientada a objetos.
Constructores, Destructores y this en C#
Aprende a usar constructores, destructores y la palabra clave this en C# para gestionar el ciclo de vida del objeto.
Fundamentos de Inyección de Dependencias en C#
Aprende los fundamentos de Inyección de Dependencias en C#, gestionando dependencias y logrando bajo acoplamiento.
Interfaces y Clases Abstractas en C#
Aprende interfaces y clases abstractas en C#, sus diferencias y cuándo usar cada una para diseñar código limpio y extensible.
Principios SOLID en C#
Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.
Structs en C# – Diferencias con las clases
Aprende las diferencias entre structs y clases en C#, incluyendo modelo de memoria, herencia y rendimiento.
Tipos Record y Objetos Inmutables en C#
Aprende record types y objetos inmutables en C#, incluyendo igualdad por valor, expresiones with y ejemplos prácticos.