Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
49m 41s
La herencia y las interfaces son parte integrante de la programación orientada a objetos (POO). Tim Corey, en su vídeo "Inheritance vs Interfaces in C#: Object Oriented Programming", ofrece una explicación detallada de cuándo utilizar la herencia y cuándo optar por las interfaces.
Este artículo sirve como una guía completa para el video de Tim Corey. Desglosa los conceptos clave, los ejemplos y las explicaciones de código que se ofrecen en el vídeo, destacando las diferencias entre herencia e interfaces y cuándo utilizar cada una de ellas.
Tim (0:00) comienza destacando la importancia de distinguir entre herencia e interfaces. Hace hincapié en la necesidad de entender cuándo utilizar cada concepto para lograr los mejores resultados. Su objetivo es demostrarlo mediante ejemplos, empezando por el uso incorrecto de la herencia única y corrigiéndolo después.
En (1:08), Tim crea una sencilla aplicación de consola utilizando .NET 5. Nombra al proyecto "OODemo". Nombra al proyecto "OODemoApp" y explica que el objetivo principal es demostrar los conceptos más que crear código listo para producción.
Tim (1:55) profundiza en los conceptos básicos de la herencia. Define la herencia como un mecanismo por el cual las propiedades y métodos de una clase base son heredados por una clase derivada. Subraya que la herencia no debe utilizarse únicamente para reutilizar y compartir código, sino para establecer una relación lógica "es-una".
Puntos clave:
Tim en (7:52) crea una clase RentalCar para ilustrar el concepto fundamental de herencia. Esta clase representa un coche de alquiler en una agencia de alquiler de Miami, Florida.
public class RentalCar
{
public int RentalId { get; set; }
public string CurrentRenter { get; set; }
public decimal PricePerDay { get; set; }
public int NumberOfPassengers { get; set; }
public void StartEngine()
{
Console.WriteLine("Turn key to ignition setting");
Console.WriteLine("Turn key to on");
}
public void StopEngine()
{
Console.WriteLine("Turn key to off");
}
}Public Class RentalCar
Public Property RentalId As Integer
Public Property CurrentRenter As String
Public Property PricePerDay As Decimal
Public Property NumberOfPassengers As Integer
Public Sub StartEngine()
Console.WriteLine("Turn key to ignition setting")
Console.WriteLine("Turn key to on")
End Sub
Public Sub StopEngine()
Console.WriteLine("Turn key to off")
End Sub
End ClassTim (10:15) explica cómo el uso incorrecto de la herencia puede dar lugar a problemas. Destaca que si la herencia se utiliza mal, puede dar lugar a un código difícil de gestionar y ampliar. Aconseja no utilizar la herencia solo para compartir código.
Tim en (10:45) añade una enumeración para los tipos de coche. Crea un nuevo archivo de clase llamado Enums.cs para mantener todas las enumeraciones en un solo lugar. Este enum ayudará a diferenciar entre los distintos estilos de vehículos.
// Enums.cs
public enum CarType
{
Hatchback,
Sedan,
Compact
}' Enums.vb
Public Enum CarType
Hatchback
Sedan
Compact
End EnumA continuación, añade una propiedad a la clase RentalCar para especificar el tipo de coche.
public class RentalCar : RentalVehicle
{
public CarType Style { get; set; }
// Other properties and methods
}Public Class RentalCar
Inherits RentalVehicle
Public Property Style As CarType
' Other properties and methods
End ClassComo explica Tim en (12:27), la agencia de alquiler decide añadir camiones a su flota, lo que introduce nuevos requisitos. Crea una clase RentalTruck que herede de la clase padre RentalVehicle.
public class RentalTruck : RentalVehicle
{
public TruckType Style { get; set; }
// Other properties and methods
}Public Class RentalTruck
Inherits RentalVehicle
Public Property Style As TruckType
' Other properties and methods
End ClassA continuación, define un nuevo enum para los tipos de camión.
// Enums.cs
public enum TruckType
{
ShortBed,
LongBed
}' Enums.vb
Public Enum TruckType
ShortBed
LongBed
End EnumTim (15:28) subraya que el hecho de que dos propiedades compartan el mismo nombre no significa que sean iguales. Lo ilustra con la propiedad Style, que podría significar diferentes enums (CarType para coches y TruckType para camiones).
La agencia de alquiler amplía su flota para incluir barcos. Tim demuestra cómo manejar esto mediante la creación de una clase RentalBoat. Inicialmente, parece manejable, ya que los barcos pueden compartir algunas propiedades con los coches y los camiones.
public class RentalBoat : RentalVehicle
{
// Properties and methods specific to boats
}Public Class RentalBoat
Inherits RentalVehicle
' Properties and methods specific to boats
End ClassLa introducción de los veleros presenta un reto, ya que los veleros no tienen motor. Tim (19:57) ilustra las limitaciones de la herencia en este caso.
public class RentalSailboat : RentalVehicle
{
public override void StartEngine()
{
throw new NotImplementedException("I do not have an engine to start");
}
public override void StopEngine()
{
throw new NotImplementedException("I do not have an engine to stop");
}
}Public Class RentalSailboat
Inherits RentalVehicle
Public Overrides Sub StartEngine()
Throw New NotImplementedException("I do not have an engine to start")
End Sub
Public Overrides Sub StopEngine()
Throw New NotImplementedException("I do not have an engine to stop")
End Sub
End ClassTim sugiere hacer que los métodos StartEngine y StopEngine sean virtuales en la clase base para permitir su anulación en clases derivadas que no utilicen estos métodos.
public abstract class RentalVehicle
{
// Common properties
public virtual void StartEngine()
{
Console.WriteLine("Engine started");
}
public virtual void StopEngine()
{
Console.WriteLine("Engine stopped");
}
}Public MustInherit Class RentalVehicle
' Common properties
Public Overridable Sub StartEngine()
Console.WriteLine("Engine started")
End Sub
Public Overridable Sub StopEngine()
Console.WriteLine("Engine stopped")
End Sub
End ClassTim explica en (21:56) los peligros de tener métodos en clases heredadas que no deben llamarse. Para el ejemplo de la clase RentalSailboat, que no tiene motor, hereda los métodos StartEngine y StopEngine de la clase RentalVehicle. Esta situación puede dar lugar a problemas si estos métodos se llaman involuntariamente, ya que deben lanzar excepciones para indicar que no son aplicables.
public class RentalSailboat : RentalVehicle
{
public override void StartEngine()
{
throw new NotImplementedException("I do not have an engine to start");
}
public override void StopEngine()
{
throw new NotImplementedException("I do not have an engine to stop");
}
}Public Class RentalSailboat
Inherits RentalVehicle
Public Overrides Sub StartEngine()
Throw New NotImplementedException("I do not have an engine to start")
End Sub
Public Overrides Sub StopEngine()
Throw New NotImplementedException("I do not have an engine to stop")
End Sub
End ClassTim en (24:06) hace hincapié en cómo la herencia puede conducir a una base de código enrevesada y desordenada cuando deja de tener sentido lógico. Por ejemplo, un velero no debe tratarse como un RentalVehicle con motor. Esto demuestra las limitaciones de la herencia y la necesidad de un mejor enfoque de diseño.
Para resolver estos problemas, Tim sugiere un mejor diseño mediante interfaces. Comienza creando un nuevo proyecto de aplicación de consola llamado "BetterOODemo" para demostrar el enfoque mejorado.
Tim presenta la interfaz IRental para encapsular propiedades comunes a todos los alquileres.
public interface IRental
{
int RentalId { get; set; }
string CurrentRenter { get; set; }
decimal PricePerDay { get; set; }
}Public Interface IRental
Property RentalId As Integer
Property CurrentRenter As String
Property PricePerDay As Decimal
End InterfaceA continuación, Tim crea una clase base para vehículos terrestres, separando el concepto de alquiler de vehículos del vehículo en sí.
public abstract class LandVehicle
{
public int NumberOfPassengers { get; set; }
public virtual void StartEngine()
{
Console.WriteLine("Engine started");
}
public virtual void StopEngine()
{
Console.WriteLine("Engine stopped");
}
}Public MustInherit Class LandVehicle
Public Property NumberOfPassengers As Integer
Public Overridable Sub StartEngine()
Console.WriteLine("Engine started")
End Sub
Public Overridable Sub StopEngine()
Console.WriteLine("Engine stopped")
End Sub
End ClassAl cambiar el nombre de la clase base del vehículo a LandVehicle, Tim se asegura de que solo los vehículos apropiados hereden los métodos relacionados con el motor.
Tim crea las clases Car y Truck que heredan de LandVehicle e implementan la interfaz IRental.
public class Car : LandVehicle, IRental
{
public int RentalId { get; set; }
public string CurrentRenter { get; set; }
public decimal PricePerDay { get; set; }
public CarType Style { get; set; }
}
public class Truck : LandVehicle, IRental
{
public int RentalId { get; set; }
public string CurrentRenter { get; set; }
public decimal PricePerDay { get; set; }
public TruckType Style { get; set; }
}Public Class Car
Inherits LandVehicle
Implements IRental
Public Property RentalId As Integer Implements IRental.RentalId
Public Property CurrentRenter As String Implements IRental.CurrentRenter
Public Property PricePerDay As Decimal Implements IRental.PricePerDay
Public Property Style As CarType
End Class
Public Class Truck
Inherits LandVehicle
Implements IRental
Public Property RentalId As Integer Implements IRental.RentalId
Public Property CurrentRenter As String Implements IRental.CurrentRenter
Public Property PricePerDay As Decimal Implements IRental.PricePerDay
Public Property Style As TruckType
End ClassEste diseño mantiene una clara separación de intereses, garantizando que cada clase solo tenga propiedades y métodos relevantes para su tipo.
Tim (31:41) habla de la importancia de evitar la duplicación innecesaria de código. Explica que, aunque la interfaz IRental requiere las mismas propiedades en varias clases, esto no se considera una violación del principio DRY (Don't Repeat Yourself) porque no se duplica ninguna lógica, solo las declaraciones de propiedades.
Tim en (35:09) explica cómo manejar la clase RentalSailboat por separado implementando la interfaz IRental, en lugar de heredar de LandVehicle. Este enfoque ayuda a evitar los escollos asociados a una herencia inapropiada.
public class Sailboat : IRental
{
public int RentalId { get; set; }
public string CurrentRenter { get; set; }
public decimal PricePerDay { get; set; }
// Additional properties and methods specific to sailboats
}Public Class Sailboat
Implements IRental
Public Property RentalId As Integer
Public Property CurrentRenter As String
Public Property PricePerDay As Decimal
' Additional properties and methods specific to sailboats
End ClassTim crea una lista para gestionar diferentes tipos de alquileres, utilizando la interfaz IRental para almacenar varios tipos de alquileres, incluidos camiones, veleros y coches.
List<IRental> rentals = new List<IRental>
{
new Truck { CurrentRenter = "Truck Renter" },
new Sailboat { CurrentRenter = "Sailboat Renter" },
new Car { CurrentRenter = "Car Renter" }
};Dim rentals As New List(Of IRental) From {
New Truck With {.CurrentRenter = "Truck Renter"},
New Sailboat With {.CurrentRenter = "Sailboat Renter"},
New Car With {.CurrentRenter = "Car Renter"}
}Este diseño permite recorrer los alquileres y acceder a propiedades comunes como CurrentRenter, PricePerDay, y RentalId.
foreach (var rental in rentals)
{
Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}For Each rental In rentals
Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}")
NextPara acceder a propiedades y métodos específicos de diferentes tipos de alquiler, Tim demuestra cómo usar la palabra clave is para verificar y convertir objetos a sus respectivos tipos.
foreach (var rental in rentals)
{
if (rental is Truck truck)
{
Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
}
else if (rental is Sailboat sailboat)
{
// Access sailboat-specific properties
}
else if (rental is Car car)
{
// Access car-specific properties
}
}For Each rental In rentals
If TypeOf rental Is Truck Then
Dim truck As Truck = CType(rental, Truck)
Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}")
ElseIf TypeOf rental Is Sailboat Then
Dim sailboat As Sailboat = CType(rental, Sailboat)
' Access sailboat-specific properties
ElseIf TypeOf rental Is Car Then
Dim car As Car = CType(rental, Car)
' Access car-specific properties
End If
NextTim hace hincapié en que el uso de interfaces proporciona flexibilidad para futuros cambios. Por ejemplo, añadir nuevos tipos de alquileres, como tanques o televisores, no alteraría la estructura existente.
Tim desaconseja el uso excesivo de la herencia para compartir código, ya que puede dar lugar a una base de código enrevesada e inflexible. En su lugar, recomienda aprovechar las interfaces y la composición para lograr los resultados deseados sin estirar la relación "is-a" más allá de sus límites lógicos.
La explicación de Tim Corey sobre la herencia y las interfaces en la programación orientada a objetos ofrece un camino claro para crear código flexible y fácil de mantener. Al mostrar los errores más comunes y ofrecer un diseño refinado con interfaces, garantiza que los desarrolladores puedan tomar decisiones informadas sobre la estructuración eficaz de sus aplicaciones.
Para profundizar en estos conceptos y verlos en acción, vea el vídeo completo de Tim. Su canal es una mina de oro de tutoriales de programación. ¡No se lo pierda!
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy