Tutorial: Actualizar interfaces con miembros de interfaz predeterminados en C # 8.0

A partir de C # 8.0 en .NET Core 3.0, puede definir una implementación cuando declara un miembro de una interfaz. El escenario más común es agregar miembros de forma segura a una interfaz ya lanzada y utilizada por innumerables clientes.


En este tutorial, aprenderá cómo:


  • Extienda las interfaces de forma segura agregando métodos con implementaciones.
  • Cree implementaciones parametrizadas para proporcionar una mayor flexibilidad.
  • Permita que los implementadores proporcionen una implementación más específica en forma de anulación.



Prerrequisitos


Deberá configurar su máquina para ejecutar .NET Core, incluido el compilador de vista previa de C # 8.0. El compilador de vista previa de C # 8.0 está disponible a partir de Visual Studio 2019 , o el último SDK de vista previa de .NET Core 3.0 . Los miembros de interfaz predeterminados están disponibles a partir de .NET Core 3.0 preview 4.


Resumen del escenario


Este tutorial comienza con la versión 1 de una biblioteca de relación con el cliente. Puede obtener la aplicación de inicio en nuestro repositorio de muestras en GitHub . La compañía que construyó esta biblioteca tenía la intención de que los clientes con aplicaciones existentes adoptaran su biblioteca. Proporcionaron definiciones mínimas de interfaz para que los usuarios de su biblioteca implementen. Aquí está la definición de interfaz para un cliente:


public interface ICustomer { IEnumerable<IOrder> PreviousOrders { get; } DateTime DateJoined { get; } DateTime? LastOrder { get; } string Name { get; } IDictionary<DateTime, string> Reminders { get; } } 

Definieron una segunda interfaz que representa un orden:


 public interface IOrder { DateTime Purchased { get; } decimal Cost { get; } } 

A partir de esas interfaces, el equipo podría construir una biblioteca para que sus usuarios creen una mejor experiencia para sus clientes. Su objetivo era crear una relación más profunda con los clientes existentes y mejorar sus relaciones con los nuevos clientes.


Ahora es el momento de actualizar la biblioteca para la próxima versión. Una de las características solicitadas permite un descuento de fidelidad para los clientes que tienen muchos pedidos. Este nuevo descuento de fidelidad se aplica cada vez que un cliente realiza un pedido. El descuento específico es una propiedad de cada cliente individual. Cada implementación de ICustomer puede establecer diferentes reglas para el descuento de lealtad.


La forma más natural de agregar esta funcionalidad es mejorar la interfaz de ICustomer con un método para aplicar cualquier descuento de lealtad. Esta sugerencia de diseño causó preocupación entre los desarrolladores experimentados: "¡Las interfaces son inmutables una vez que se han lanzado! ¡Este es un cambio radical! C # 8.0 agrega implementaciones de interfaz predeterminadas para actualizar las interfaces. Los autores de la biblioteca pueden agregar nuevos miembros a la interfaz y proporcionar una implementación predeterminada para esos miembros.


Las implementaciones de interfaz predeterminadas permiten a los desarrolladores actualizar una interfaz y al mismo tiempo permitir que cualquier implementador anule esa implementación. Los usuarios de la biblioteca pueden aceptar la implementación predeterminada como un cambio sin interrupciones. Si sus reglas comerciales son diferentes, pueden anularlas.


Actualizar con miembros de interfaz predeterminados


El equipo acordó la implementación predeterminada más probable: un descuento de lealtad para los clientes.


La actualización debe proporcionar la funcionalidad para establecer dos propiedades: el número de pedidos necesarios para ser elegible para el descuento y el porcentaje del descuento. Esto lo convierte en un escenario perfecto para los miembros de interfaz predeterminados. Puede agregar un método a la interfaz de ICustomer y proporcionar la implementación más probable. Todas las implementaciones existentes y nuevas pueden usar la implementación predeterminada o proporcionar la suya propia.


Primero, agregue el nuevo método a la implementación:


 // Version 1: public decimal ComputeLoyaltyDiscount() { DateTime TwoYearsAgo = DateTime.Now.AddYears(-2); if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10)) { return 0.10m; } return 0; } 

El autor de la biblioteca escribió una primera prueba para verificar la implementación:


 SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31)) { Reminders = { { new DateTime(2010, 08, 12), "childs's birthday" }, { new DateTime(1012, 11, 15), "anniversary" } } }; SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m); c.AddOrder(o); o = new SampleOrder(new DateTime(2103, 7, 4), 25m); c.AddOrder(o); // Check the discount: ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Observe la siguiente parte de la prueba:


 // Check the discount: ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Ese reparto de SampleCustomer a ICustomer es necesario. La clase SampleCustomer no necesita proporcionar una implementación para ComputeLoyaltyDiscount ; eso es provisto por la interfaz ICustomer . Sin embargo, la clase SampleCustomer no hereda miembros de sus interfaces. Esa regla no ha cambiado. Para llamar a cualquier método declarado e implementado en la interfaz, la variable debe ser el tipo de interfaz, ICustomer en este ejemplo.


Proporcionar parametrización


Ese es un buen comienzo. Pero, la implementación predeterminada es demasiado restrictiva. Muchos consumidores de este sistema pueden elegir diferentes umbrales para el número de compras, una duración de membresía diferente o un porcentaje de descuento diferente. Puede proporcionar una mejor experiencia de actualización para más clientes al proporcionar una forma de establecer esos parámetros. Agreguemos un método estático que establece esos tres parámetros que controlan la implementación predeterminada:


 // Version 2: public static void SetLoyaltyThresholds( TimeSpan ago, int minimumOrders = 10, decimal percentageDiscount = 0.10m) { length = ago; orderCount = minimumOrders; discountPercent = percentageDiscount; } private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years private static int orderCount = 10; private static decimal discountPercent = 0.10m; public decimal ComputeLoyaltyDiscount() { DateTime start = DateTime.Now - length; if ((DateJoined < start) && (PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; } 

Hay muchas nuevas capacidades de lenguaje que se muestran en ese pequeño fragmento de código. Las interfaces ahora pueden incluir miembros estáticos, incluidos campos y métodos. También se habilitan diferentes modificadores de acceso. Los campos adicionales son privados, el nuevo método es público. Cualquiera de los modificadores está permitido en los miembros de la interfaz.


Las aplicaciones que usan la fórmula general para calcular el descuento de lealtad, pero diferentes parámetros, no necesitan proporcionar una implementación personalizada; Pueden establecer los argumentos a través de un método estático. Por ejemplo, el siguiente código establece una "apreciación del cliente" que recompensa a cualquier cliente con más de un mes de membresía:


 ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m); Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Amplíe la implementación predeterminada


El código que ha agregado hasta ahora ha proporcionado una implementación conveniente para aquellos escenarios en los que los usuarios desean algo como la implementación predeterminada o para proporcionar un conjunto de reglas no relacionadas. Para una característica final, refactoricemos un poco el código para habilitar escenarios en los que los usuarios quieran construir sobre la implementación predeterminada.


Considere una startup que quiera atraer nuevos clientes. Ofrecen un descuento del 50% en el primer pedido de un nuevo cliente. De lo contrario, los clientes existentes obtienen el descuento estándar. El autor de la biblioteca necesita mover la implementación predeterminada a un método protected static para que cualquier clase que implemente esta interfaz pueda reutilizar el código en su implementación. La implementación predeterminada del miembro de la interfaz también llama a este método compartido:


 public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this); protected static decimal DefaultLoyaltyDiscount(ICustomer c) { DateTime start = DateTime.Now - length; if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; } 

En una implementación de una clase que implementa esta interfaz, la anulación puede llamar al método auxiliar estático y extender esa lógica para proporcionar el descuento de "nuevo cliente":


 public decimal ComputeLoyaltyDiscount() { if (PreviousOrders.Any() == false) return 0.50m; else return ICustomer.DefaultLoyaltyDiscount(this); } 

Puede ver el código completo en nuestro [repositorio de muestras en GitHub] (puede obtener la aplicación de inicio en nuestro repositorio de muestras en GitHub .


Estas nuevas características significan que las interfaces se pueden actualizar de forma segura cuando hay una implementación predeterminada razonable para esos nuevos miembros. Diseñe cuidadosamente las interfaces para expresar ideas funcionales únicas que puedan ser implementadas por múltiples clases. Eso facilita la actualización de esas definiciones de interfaz cuando se descubren nuevos requisitos para esa misma idea funcional.

Source: https://habr.com/ru/post/456238/


All Articles