Come non usare le interfacce in C #

Quando si progetta un'applicazione, sarà spesso necessario utilizzare interfacce e classi astratte. Questo articolo discute alcuni esempi comuni di "abuso dell'interfaccia" e le strategie che possiamo utilizzare per evitarli. Discute anche cosa si intende per principio, "programma su un'interfaccia e non su un'implementazione".

Cosa sono le interfacce?

Prima di tutto, comprendiamo le interfacce e il motivo per cui sono necessarie nella programmazione. Un'interfaccia è strettamente un contratto; non ha alcuna implementazione. Un'interfaccia contiene solo dichiarazioni di membri. È possibile avere dichiarazioni di metodo ma non definizioni. I membri dichiarati in un'interfaccia dovrebbero essere implementati nei tipi (classi e strutture) che estendono o implementano l'interfaccia. Un'interfaccia non può contenere campi. Un'interfaccia non può essere serializzata perché non può avere membri dati. Come ho detto, un'interfaccia può avere solo dichiarazioni e non definizioni.  

Evita di apportare modifiche alle interfacce 

Una classe o una struttura che estende un'interfaccia dovrebbe implementare tutti i suoi membri. Se l'implementazione cambia, il codice continuerà a funzionare. Tuttavia, se il contratto, cioè l'interfaccia, cambia, allora dovrai cambiare le implementazioni di tutti i tipi che estendono l'interfaccia. In altre parole, qualsiasi modifica all'interfaccia avrà un impatto su tutti i tipi che estendono l'interfaccia. I tipi che estendono l'interfaccia devono aderire al contratto. Quindi, usa le interfacce solo quando raramente hai bisogno di cambiarle. Inoltre, è generalmente meglio creare una nuova interfaccia piuttosto che modificarne una esistente. 

Programma su un'interfaccia, non su un'implementazione

Potreste aver sentito le parole "programma su un'interfaccia e non su un'implementazione" di tanto in tanto. Potresti aver utilizzato le interfacce nel tuo codice ma stavi ancora programmando l'implementazione. Esaminiamo ora la differenza tra i due approcci.

Quando si programma su un'interfaccia si utilizza l'astrazione più generica (un'interfaccia o una classe astratta) invece di un'implementazione concreta. Poiché le interfacce garantiscono l'uniformità, la programmazione di un'interfaccia implica la possibilità di gestire oggetti simili in modo uniforme. In tal modo, sei separato dall'implementazione, ovvero le tue implementazioni possono variare. Ciò aggiunge flessibilità anche ai tuoi progetti.

Il frammento di codice seguente illustra la programmazione su un'interfaccia. Considera un'interfaccia chiamata IRepository che contiene la dichiarazione di alcuni metodi. Le classi ProductRepository e CustomerRepository estendono l'interfaccia IRepository e implementano i metodi dichiarati nell'interfaccia IRepository, come mostrato di seguito.

interfaccia pubblica IRepository

    {

        // Un po 'di codice

    }

    public class ProductRepository: IRepository

    {

        // Un po 'di codice

    }

    public class CustomerRepository: IRepository

    {

        // Un po 'di codice

    }

Il codice seguente può essere utilizzato per creare un'istanza di ProductRepository.

Repository IRepository = new ProductRepository ();

L'idea è che puoi usare qualsiasi classe qui che implementa l'interfaccia IRepository. Quindi, anche la seguente dichiarazione è valida.

Repository IRepository = nuovo CustomerRepository ();

Quando si programma a un'implementazione, questa uniformità viene persa. Invece, in genere si avranno alcuni costrutti, come le istruzioni "if..else" o "switch..case", per controllare il comportamento nel codice.

Evita un uso eccessivo delle interfacce

Associare ogni classe a un'interfaccia non è una buona pratica. Un uso eccessivo delle interfacce in questo modo crea complessità inutili, introduce ridondanza del codice, viola YAGNI e riduce la leggibilità e la manutenibilità della base di codice. Le interfacce vengono utilizzate per raggruppare oggetti che hanno un comportamento identico. Se gli oggetti non hanno un comportamento identico, non è necessario questo raggruppamento. L'uso delle interfacce quando non si hanno più implementazioni è un esempio di uso eccessivo dell'interfaccia.

La creazione di un'interfaccia per una classe che corrisponda ai membri pubblici della classe è abbastanza comune. In tal modo non si aggiunge alcun valore - si limita a duplicare l'interfaccia della classe senza aggiungere alcuna astrazione reale.

Vediamo ora un esempio di come le interfacce vengono utilizzate in modo eccessivo. Considera la seguente interfaccia denominata IProduct.

interfaccia pubblica IProduct

    {

        int Id {get; impostato; }

        string ProductName {get; impostato; }

        doppio prezzo {get; impostato; }

        int Quantità {get; impostato; }

    }

La classe Product estende l'interfaccia IProduct come mostrato di seguito.

public class Prodotto: IProduct

    {

        public int Id {get; impostato; }

        stringa pubblica ProductName {get; impostato; }

        pubblico doppio prezzo {get; impostato; }

        public int Quantità {get; impostato; }

    }

Chiaramente, non abbiamo bisogno dell'interfaccia IProduct, poiché l'interfaccia e la sua implementazione sono identiche. Il codice ridondante non è necessario.

Diamo un'occhiata a un altro esempio. Il frammento di codice seguente mostra un'interfaccia denominata IProductManager con la dichiarazione di due metodi, ovvero Salva e Aggiorna.

 interfaccia pubblica IProductManager

    {

        void Save (prodotto IProduct);

        void Update (prodotto IProduct);

    }

L'interfaccia IProductManager contiene le dichiarazioni dei metodi pubblici della classe ProductManager. Ecco come appare la classe ProductManager.

 classe pubblica ProductManager: IProductManager

    {

        public void Save (prodotto IProduct)

        {

           // Scrivi qui la tua implementazione

        }

        public void Update (prodotto IProduct)

        {

            // Scrivi qui la tua implementazione

        }

    }

Le interfacce IProduct e IProductManager sono esempi di utilizzo eccessivo dell'interfaccia. Entrambe queste interfacce hanno un'unica implementazione e non aggiungono alcun valore.

Utilizzando le interfacce, è possibile rimuovere gli accoppiamenti non necessari nel codice e rendere il codice facilmente testabile. Tuttavia, dovrebbe essere evitato un uso eccessivo delle interfacce. Usa le interfacce solo quando ci sarà più di una loro implementazione. Puoi anche utilizzare le interfacce quando hai una classe che ha molti ruoli da svolgere o che ha più responsabilità. In questo caso la tua classe può implementare più interfacce, una per ogni ruolo.