Aplicación del Principio de Abierto/Cerrado en Programación
Clase 8 de 16 • Curso de Principios SOLID en C# y .NET
Contenido del curso
Clase 8 de 16 • Curso de Principios SOLID en C# y .NET
Contenido del curso
Alfonso Navarro
Miguel Angel Reyes Moreno
Daniel Meza
Eduardo Luis Mexia Angulo
Miguel Angel Reyes Moreno
Andres Felipe Osorio Molina
José Ángel Soriano Soriano
Francisco Murillo
BALFRE VAZQUEZ CASTREJON
Luis G. Julio
JUAN SILVA
Miguel Santiago Gómez Suárez
Franco David Perez
Miguel Teheran
Leonardo Valdivieso
Sonsire Vidal B.
César Augusto Poveda Floréz
Santiago Rodriguez Palacio
ROMMEL DUAREZ SAENZ
Oseas Pozo
Jose Gabriel Zaragoza
Carlos Madrigal Ramos
Miguel Teheran
Emilio Nicolás Mendoza Patti
Miguel Teheran
Manuel Herrera
Miguel Teheran
Jonathan Harley Sabogal toro
José Luis Hernández
Irwin Morales Cabrera
La respuesta del reto implementando la "interface"
Interface Employee
namespace OpenClose { public interface IEmployee { public string Fullname { get; set; } public int HoursWorked { get; set; } public decimal CalculateSalaryMonthly(); } }
Y en las clases
Método EmployeePartTime
public class EmployeePartTime : IEmployee { public string Fullname { get ; set ; } public int HoursWorked { get ; set ; } public EmployeePartTime(string fullname, int hoursWorked) { Fullname = fullname; HoursWorked = hoursWorked; } public decimal CalculateSalaryMonthly() { decimal hourValue = 20000M; decimal salary = hourValue * HoursWorked; if (HoursWorked > 160) { decimal effortCompensation = 5000M; int extraDays = HoursWorked - 160; salary += effortCompensation * extraDays; } return salary; } }
Método EmployeeFullTime
public class EmployeeFullTime : IEmployee { public string Fullname { get ; set ; } public int HoursWorked { get ; set; } public EmployeeFullTime(string fullname, int hoursWorked) { Fullname = fullname; HoursWorked = hoursWorked; } public decimal CalculateSalaryMonthly() { decimal hourValue = 30000M; decimal salary = hourValue * HoursWorked; return salary; } }
y program
using OpenClose; CalculateSalaryMonthly(new List<IEmployee>() { new EmployeeFullTime("Pepito Pérez", 160), new EmployeePartTime("Manuel Lopera", 180) }); void CalculateSalaryMonthly(List<IEmployee> employees) { foreach (var employee in employees) { Console.WriteLine($"Empleado: {employee.Fullname}, Pago: {employee.CalculateSalaryMonthly()} "); } }
Los atributos de una interfaz siempre son públicos, no es necesario agregar 'public' antes.
namespace OpenClose { public interface IEmployee { string Fullname { get; set; } int HoursWorked { get; set; } decimal CalculateSalaryMonthly(); } }
El concepto de clase Abstracta siempre me ha costado trabajo. Este ejercicio, deja más claro de qué se trata
igual a mi
Es simplemente una clase que no será instanciada. Por ejemplo, en un ecommerce tenemos la clase Producto, donde tendremos todos los atributos de las cosas que vamos a vender. Y de ahí pueden extender otras cosas, como laptops, ropa, etc. La clase Producto será una clase abstracta porque como tal no vamos a requerir hacer una instancia de esta clase, ya que no vendemos un 'producto' como tal, sino una cosa muy específica.
En el CalculateSalaryMonthly() de la clase EmployeePartTime hace falta las lineas de código para calcular las horas extras cuando es mayor a 160
if (HoursWorked > 160) { decimal effortCompensation = 5000M; int extraDays = HoursWorked - 160; salary += effortCompensation * extraDays; }
cierto, pero una chulada la explicacion
asi quisiera ganar yo al mes😢... $4,800,000.0
Yo cambie la clase abstracta porque el salario considero debe ir dentro
namespace OpenClose; public abstract class Employee { public string Fullname { get; } public int HoursWorked { get; } public decimal HourValue { get; } public Employee(string fullname, int hoursWorked, decimal hourValue) { Fullname = fullname; HoursWorked = hoursWorked; HourValue = hourValue; } public abstract decimal CalculateMonthlySalary(); }
public class EmployeeFullTime : Employee { public EmployeeFullTime(string fullname, int hoursWorked, decimal hourValue) : base(fullname, hoursWorked, hourValue) { } public override decimal CalculateMonthlySalary() { return HourValue * HoursWorked; } }
public class EmployeePartTime : Employee { public int hoursLimitForExtraCompensation { get; set; } = 160; public decimal effortCompensation { get; set; } = 5000M; public EmployeePartTime(string fullname, int hoursWorked, decimal hourValue) : base(fullname, hoursWorked, hourValue) { } public override decimal CalculateMonthlySalary() { var salary = HourValue * HoursWorked; if (HoursWorked > hoursLimitForExtraCompensation) { int extraDays = HoursWorked - hoursLimitForExtraCompensation; salary += effortCompensation * extraDays; } return salary; } }
Gracias, ahora si pude comprender lo de las Clases Abstractas, les dejo una pagina muy interesante
Personalmente, recomendaría utilizar interfaces en lugar de clases abstractas en proyectos de C#. Las interfaces ofrecen flexibilidad y versatilidad en el diseño de tu aplicación, ya que una clase puede implementar múltiples interfaces. Esto te permite componer y reutilizar código de manera más efectiva.
Además, el uso de interfaces promueve la separación de preocupaciones y un diseño más modular, lo cual facilita el mantenimiento y la prueba de tu código.
Las interfaces son especialmente útiles en escenarios donde la herencia múltiple de clases no está permitida en C#. Al implementar interfaces, puedes definir contratos claros y comportamientos esperados sin estar limitado a una única clase base.
Además, el uso de interfaces reduce el acoplamiento entre componentes y te brinda un diseño más flexible y mantenible. Al depender de contratos en lugar de implementaciones concretas, puedes realizar cambios futuros sin afectar otras partes del sistema.
Me pareció super interesante la implementación de clases abstractas e interfaces. Cabe aclarar que las interfaces se implementan muy similar entre lenguajes de programación. Para C#, la inteligencia artificial de notion me deja el siguiente ejemplo: Las clases abstractas y las interfaces son herramientas utilizadas en la programación orientada a objetos para facilitar la creación de componentes con comportamientos comunes.
Clases abstractas:
Un ejemplo de una clase abstracta en C#:
public abstract class Animal { public abstract void MakeSound(); public void Sleep() { Console.WriteLine("Zzzzz"); } } public class Dog : Animal { public override void MakeSound() { Console.WriteLine("Woof!"); } } public class Cat : Animal { public override void MakeSound() { Console.WriteLine("Meow!"); } }
En este ejemplo, Animal es una clase abstracta que contiene un método abstracto MakeSound() y un método con implementación Sleep(). La clase Dog y la clase Cat heredan de Animal y proporcionan su propia implementación del método MakeSound().
Interfaces:
Un ejemplo de una interfaz en C#:
public interface IShape { double Area(); double Perimeter(); } public class Rectangle : IShape { public double Width { get; set; } public double Height { get; set; } public double Area() { return Width * Height; } public double Perimeter() { return 2 * (Width + Height); } } public class Circle : IShape { public double Radius { get; set; } public double Area() { return Math.PI * Radius * Radius; } public double Perimeter() { return 2 * Math.PI * Radius; } }
En este ejemplo, IShape es una interfaz que especifica dos métodos Area() y Perimeter(). La clase Rectangle y la clase Circle implementan IShape y proporcionan su propia implementación de los métodos Area() y Perimeter().
Cabe aclarar que, para resolver el reto por medio de interfaces, es necesario implementar todo lo que posee un interfaz, ya sean métodos o propiedades. Dejo un ejemplo de la implementación del EmpleadoTiempoCompleto:
namespace OpenClose { public class EmployeeFullTime : IEmployee { public string Fullname { get ; set ; } public int HoursWorked { get ; set ; } public EmployeeFullTime(string fullname, int hoursWorked) { Fullname = fullname; HoursWorked = hoursWorked; } public decimal CalculateSalaryMonthly() { decimal hourValue = 30000M; decimal salary = hourValue * HoursWorked; return salary; } } }
Se podría crear el constructor en la clase abstracta? y llamar a :base() en cada subclase?
Si, es un tecnica bastante usanda cuando se crean sobretodo componentes genericos altamente complejos
La respuesta al reto:
interface IEmployee { decimal CalculateSalaryMonthly(); string Fullname { get; set; } } public abstract class Employee: IEmployee { public string Fullname { get; set; } = ""; public int HoursWorked { get; set; } public abstract decimal CalculateSalaryMonthly(); }
Entonces la subclase queda asi:
public class EmployeeContractor: Employee { public EmployeeContractor(string fullname, int hoursWorked) { Fullname = fullname; HoursWorked = hoursWorked; } public override decimal CalculateSalaryMonthly() { decimal hourValue = 34000M; decimal salary = hourValue * HoursWorked; return salary; } }
Y el programa:
ShowSalaryMonthly(new List<IEmployee>() { new EmployeeFullTime("Pepito Pérez", 160), new EmployeeContractor("Mariana Gómez", 140), new EmployeePartTime("Manuel Lopera", 180) });
Considero que el calculo de CalculateSalaryMonthly en EmployeePartTime no estuvo del todo bien cuando se extrajo desde el Program, ya que en el Program tenía una consideración adicional por esfuerzo por compensanción, que por el copy & paste del calculo en EmployeeFullTime se omitió.
Hola, de acuerdo de hecho considero que muchas veces hay que tener cuidado con el copiar, pegar porque se presta para estas situaciones. Si ese código fuera para algo real, generaría problemas graves, porque eso seria considerado un bug y no daría los resultados esperados, estaría calculando de manera incorrecta el salario para ese tipo de empleados. Pero bueno, todos somos humanos y nos podemos equivocar, lo importante es darnos cuenta, y demostrarnos a nosotros mismos que estamos aprendiendo.
¿Por qué es crucial refactorizar el código?
Refactorizar el código es crucial para evitar que la lógica de tu programa se vuelva inmanejable y viole principios como el de Responsabilidad Única. Al aplicar el Principio Abierto/Cerrado, permites que tu aplicación crezca con nuevas funcionalidades sin necesidad de modificar el código ya existente, haciéndolo más robusto y fácil de mantener.
para que sirve el :C1 al imprimir??
Formato de cadena para decimales
Excelente la clase y lo que di vueltas... al final hice una mezcla ya que varios tipos de empleados tienen la misma forma de calcular el salario ahí utilicé la clase abstracta que implementa le horas x $ y en el caso del partialTime ahí se hace un overrride... y para más placer visual lo hice virtual y metí la propiedad abstracta de HourValue como protected para que cada clase hija la tenga que definir...
namespace OpenClose { public abstract class EmployeeBase : IEmployee { public string Fullname { get; } public int HoursWorked { get; } protected abstract decimal HourValue { get; } protected EmployeeBase(string fullname, int hoursWorked) { Fullname = fullname; HoursWorked = hoursWorked; } public virtual decimal CalculateSalaryMonthly() { return HourValue * HoursWorked; } } } namespace OpenClose { public interface IEmployee { string Fullname { get; } int HoursWorked { get; } decimal CalculateSalaryMonthly(); } } namespace OpenClose { public class EmployeeFullTime : EmployeeBase { protected override decimal HourValue => 30000M; public EmployeeFullTime(string fullname, int hoursWorked) : base(fullname, hoursWorked) { } } } namespace OpenClose { public class EmployeeContractor : EmployeeBase { protected override decimal HourValue => 100000M; public EmployeeContractor(string fullname, int hoursWorked) : base(fullname, hoursWorked) { } } } namespace OpenClose { public class EmployeeContractor : EmployeeBase { protected override decimal HourValue => 100000M; public EmployeeContractor(string fullname, int hoursWorked) : base(fullname, hoursWorked) { } } } using OpenClose; ShowSalary(new List<IEmployee>() { new EmployeeFullTime("Pepito Pérez", 160), new EmployeePartTime("Manuel Lopera", 180), new EmployeeContractor("Juan Pérez", 200) }); void ShowSalary(List<IEmployee> employees) { foreach (var employee in employees) { decimal salary = employee.CalculateSalaryMonthly(); Console.WriteLine($"Empleado: {employee.Fullname}, Pago: {salary:C1} "); } }
En este punto es indiferente usar Interface o Clase para implementar Employee? Funcionaria exactamente igual para este ejemplo o me equivoco??
Lo ideal es usar tipos abstractos lo mas que se pueda pero el concepto de abierto cerrardo aplica para todos los elementos de un software que use POO.
Estaria bien que el metodo ShowSalaryMonthly quede en program, o deberiamos llevarla a una clase propia ?
Lo ideal es estructurar mejor el código que se pueda lo hicimos por ser un demo y por ser una aplicación de consola, pero lo ideal es moverlo a diferentes archivos, tener un archivo con la lógica para pintar en la interfaz.
Hola, una pregunta: ¿Qué diferencia hay entre usar una clase abstracta y usar una interfaz?
Las clases abstractas permiten realizar implementaciones base que se pueden usar en todas las clases que hereden. Anteriormente no era posible hacer algún tipo de implentación en las interfaces en C# pero eso cambió con un nuevo feature. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods
public interface IEmployee { public string Name { get; set; } public int HoursWorked { get; set; } public decimal CalculateSalaryMonthly(int hoursWorked); } ``` public interface IEmployee { public string Name { get; set; } public int HoursWorked { get; set; } public decimal CalculateSalaryMonthly(int hoursWorked); }
Me gusto el demo, muy simple de entender.
Excelente explicación