Fermer

novembre 29, 2022

La 1001e pièce de votre puzzle de 1000 pièces : les fonctions d’interface par défaut de .NET


J’ai récemment travaillé avec un client qui souhaitait ajouter à Optimizely un sous-système raisonnablement important qui ajouterait une gestion automatisée à son contenu. En coupant le code pour cela, je me suis retrouvé à écrire un code similaire dans plusieurs classes. J’ai dû l’écrire de cette façon : 1) Le client était actuellement sur CMS11 et n’avait pas accès aux nouvelles fonctionnalités du langage ; 2) La hiérarchie des classes m’a empêché d’insérer un ancêtre commun. Heureusement, .NET a étendu les fonctionnalités des interfaces, afin que nous puissions tirer parti de celles d’Optimizely.

Avec .NET 5, Microsoft a introduit des implémentations par défaut sur les interfaces. Désormais, les interfaces peuvent apporter une implémentation par défaut. Toutes les classes implémentant l’interface peuvent utiliser l’implémentation par défaut ou la remplacer par une logique personnalisée. Assez de texte ! Codons !

Interface d’origine

Le code suivant est quelque chose que nous créons pour une expérience Optimizely :

using OptimizelySDK;
using OptimizelySDK.Entity;

namespace Teapot.Interfaces.Services
{
    public interface IExperimentation
    {
             
        public OptimizelyUserContext CreateUserContext(UserAttributes userAttributes = null, EventTags eventTags = null);
        public string GetUserId();

        public void TrackEvent(string eventKey);
    }
}

Il n’y a pas grand-chose à voir ici; c’est comme toutes les autres interfaces que vous avez écrites.

Interface avec l’implémentation par défaut de .NET

Avec cette mise à jour de l’interface, j’ai ajouté le code par défaut à la fonction GetUserId. Cela fera plusieurs choses : centraliser le code répété (n’oubliez pas de le garder SEC), et cette fonction n’aura pas besoin d’être implémentée séparément lorsqu’une classe utilise l’interface.

using OptimizelySDK;
using OptimizelySDK.Entity;
using Perficient.Infrastructure.Interfaces.Services;
using System;


namespace Teapot.Interfaces.Services
{
    public interface IExperimentation
    {
             
        public OptimizelyUserContext CreateUserContext(UserAttributes userAttributes = null, EventTags eventTags = null);
        public string GetUserId(ICookieService cookieService)
        {
            var userId = cookieService.Get("opti-experiment-testA");
            if (userId == null)
            {
                userId = Guid.NewGuid().ToString();
                cookieService.Set("opti-experiment-testA", userId);
            }


            return userId;
        }


        public void TrackEvent(string eventKey);
    }
}

L’implémentation par défaut peut être remplacée si nécessaire, comme pour toutes les fonctions d’interface. Qui n’a pas rencontré une exception à la règle dans son code ?

Utilisation de l’implémentation par défaut

L’objet doit être converti en type d’interface pour appeler l’implémentation par défaut. Sinon, le runtime recherchera une version remplacée de la fonction sur l’entité d’implémentation.

return _featureExpermentation.CreateUserContext((this as IExperimentation).GetUserId(_cookieService));

Si l’implémentation par défaut est appelée souvent, cela crée un code laid. Cela peut être résolu avec un peu de sucre syntaxique dans la classe en tirant parti de l’interface :

public string GetUserId()
{
    return (this as IExperimentation).GetUserId(_cookieService);
}

Les pensées

Je voulais publier ceci plus tôt, mais j’ai passé du temps à réfléchir à la place de ce changement de paradigme dans mon architecture Opti actuelle. « Cela ne correspond pas à la vision traditionnelle de la conception pilotée par le domaine ! » était ma première pensée. Autant j’étais enthousiasmé par cette nouvelle fonctionnalité de langage, autant je pensais que ce serait la pince à bec de cigogne dans ma boîte à outils de codage. Alors que j’essayais quelques choses et que je bricolais avec les fonctions par défaut, j’ai repensé à toutes les fois où j’ai écrit des parties dans des services autonomes, sans arguments ni services nécessaires pour faire le travail. Ce sont les petits endroits que je pense qu’une implémentation d’interface par défaut peut remplir. Soulager une partie du gonflement (nous n’obtiendrons jamais tout) des services.

Ma règle d’or à partir d’ici est que si une méthode agit sur l’objet sans dépendances extérieures, il peut s’agir d’une fonction par défaut sur l’interface appropriée.






Source link

novembre 29, 2022