Suggerimenti per la progettazione: come progettare una lezione di wrap

2

Sto usando C # su una soluzione piuttosto grande che usa una classe statica per gestire la sessione sul database.

Le prestazioni non sono così buone a causa del numero di chiamate al database.

L'azienda decide di introdurre Redis come alternativa molto veloce. Certo che è così veloce! Ma ora devo progettare un servizio che sostituirà gradualmente la Static Class originale.

Nella soluzione non esiste un DI system.

Sto chiedendo alcune indicazioni per progettare il nuovo "Session Manager" che riassume l'uso della Classe statica piuttosto che il client Redis. In base all'operazione, chiamerà internamente un metodo piuttosto l'altro.

C'è qualche motivo per ottenere consigli da?

    
posta AngeloBad 21.11.2017 - 16:14
fonte

3 risposte

3

La soluzione fornita nel libro Legacy Code di Mike Feather è molto simile alle soluzioni fornite sopra, ma senza alcuna necessità di cambiare i consumatori della tua classe statica.

Iniziamo con il semplice esempio dato da John Wu, con piccole modifiche (presumo che anche la classe sia marcata static ):

public static class SessionHelper
{
    public static void Foo()
    {
        //Implementation
    }

    public static void Bar()
    {
        //Implementation
    }
}

Estrai l'interfaccia, crea una nuova classe (non statica) implementandola spostando tutti i dettagli di implementazione dalla classe statica nella nuova classe e rimuovendone i modificatori static . Genera anche una classe simile usando Redis invece del database:

public interface ISessionHelper
{
    void Foo();
    void Bar();
}

class DatabaseSessionHelper : ISessionHelper
{
    public void Foo() 
    {
        //Implementation taken from the static class
    }

    public void Bar() 
    {
        //Implementation taken from the static class
    }
}


class RedisSessionHelper : ISessionHelper
{
    public void Foo() 
    {
        //new Implementation based on Redis
    }

    public void Bar() 
    {
        //new Implementation based on Redis
    }
}

Ora torna alla tua classe statica originale, aggiungi un nuovo membro statico del tipo di interfaccia, crea un'istanza di DatabaseSessionHelper per impostazione predefinita, ma consenti l'iniezione di un'istanza diversa e inoltra tutte le chiamate all'istanza:

public static class SessionHelper
{
    private static ISessionHelper _Instance = new DatabaseSessionHelper();

    public static void SetInstance(ISessionHelper instance)
    {
        _Instance = instance;
    }

    public static void Foo()
    {
        _Instance.Foo();
    }

    public static void Bar()
    {
        _Instance.Bar();
    }
}

Ora puoi impostare l'istanza su Redis da qualche parte durante l'avvio dell'applicazione: SessionHelper.SetInstance(new RedisSessionHelper()) . Questa è l'unica modifica richiesta nel resto del programma, non è necessario modificare nient'altro, il resto del programma non avrà bisogno di sapere nulla delle modifiche nella classe statica. Questa è la differenza rispetto alle altre soluzioni fornite qui, che richiedono modifiche altrove.

    
risposta data 22.11.2017 - 11:52
fonte
3

Supponiamo che tu abbia una libreria statica come questa:

class SessionHelper
{
    static public void Foo()
    {
        //Implementation
    }

    static public void Bar()
    {
        //Implementation
    }
}

E un programma che lo usa, che preferiresti non scimmi troppo:

class MassiveSpaghettiCode
{
    void DoSomethingNobodyUnderstands()
    {
        SessionHelper.Foo();
        SessionHelper.Bar();
    }
}

Vorrei estrarre i metodi statici in un'interfaccia e copiare il codice di implementazione dalla classe statica, esponendo solo ogni metodo come membro dell'interfaccia.

interface ISessionHelper
{
    void Foo();
    void Bar();
}

class SessionHelper : ISessionHelper
{
    public void Foo() 
    {
        //Implementation
    }

    public void Bar() 
    {
        //Implementation
    }
}

Quindi esporre un'istanza dell'implementazione tramite una proprietà membro con lo stesso nome della classe statica originale. Ciò consente al codice spaghetti precedente di continuare a funzionare senza alcuna modifica. Per i punti bonus, possiamo iniettare l'istanza se vogliamo.

class MassiveSpaghettiCode
{
    protected readonly ISessionHelper _sessionHelper;

    protected ISessionHelper SessionHelper 
    {
        get
        {
            return _sessionHelper;
        }
    }            

    public MassiveSpaghettiCode() : this( new SessionHelper() ) //Default constructor requires no injection
    {
    }

    public MassiveSpaghettiCode(ISessionHelper sessionHelper)
    {
        _sessionHelper = sessionHelper; //Injected, or injectable at least
    }

    void DoSomethingNobodyUnderstands()
    {
        SessionHelper.Foo();  //Notice this code hasn't changed at all
        SessionHelper.Bar();
    }
}

Una volta che il codice esistente è stato estratto in un'interfaccia / implementazione, scrivi una serie di test di integrazione e rendilo bello e stabile. Vuoi assicurarti di capire cosa significhi soddisfare il contratto implicito da tale interfaccia, non solo sintatticamente ma semanticamente.

Una volta che hai compreso a fondo l'interfaccia implementata dal codice legacy, crea una nuova classe, implementando la stessa interfaccia, ma con un codice nuovo utilizzando la tua nuova tecnologia di back-end (Redis, o qualsiasi altra cosa). Esegui questa lezione attraverso esattamente gli stessi test di unità. Assicurati di poter spiegare qualsiasi differenza nei risultati del test; idealmente non dovrebbe essercene.

Una volta che tutti i test passano in modo identico, sei pronto per disattivarlo, il che è facile come cambiare la dipendenza.

P.S. Credo che questo sia più o meno l'approccio adottato da Microsoft durante la migrazione di tutti gli utenti da ASP classico a ASP.NET, ad es. la sintassi Session e Response sembrava la stessa, ma funzionava in modo piuttosto diverso.

    
risposta data 21.11.2017 - 20:51
fonte
1

Crea un'interfaccia per la funzionalità. Userò un esempio basato su una chiamata al file system statico, ma i concetti sono gli stessi per il tuo problema con Redis.

public interface IFileSystem
{
   string ReadAllText(string path);
}

Ora quello vero:

public class RealFileSystem : IFileSystem
{
   public ReadAllText(string path)
   {
     System.IO.File.ReadAllText(path);
   }
}

Un'alternativa (falso):

public class FakeFileSystem: IFileSystem 
{
  public ReadAllText(string path)
  {
    return "Fake response from: " + path;
  }
}

Ora, sarà necessario estrarre tale dipendenza, sia nel costruttore che nel metodo che la chiama.

Come callout di esempio, usiamo l'iniezione a livello di metodo:

public string MethodLevelInjection(IFileSystem fileSystem, string path)
{
  fileSystem.ReadAllText(path);
}

Ora, chiunque chiami questo metodo dovrà creare l'istanza corretta e passarla. I test possono usare un'implementazione falsa.

Quindi, si dovrà utilizzare il metodo method o la dipendenza dalla dipendenza del livello di costruzione per passare la dipendenza. Si potrebbe introdurre un'infrastruttura per le dipendenze per gestire il wire up o farlo manualmente. Se questo è l'unico caso, forse manualmente è il migliore finché non ci sono più istanze di DI necessarie.

Ma ora si ha la possibilità di cambiare la dipendenza.

    
risposta data 21.11.2017 - 18:55
fonte

Leggi altre domande sui tag