Come gestire lo stato di un oggetto? E come arricchire la logica di transizione dello stato?

0

Sto progettando una classe che ha uno stato. Mi chiedo se dovrei esporre questo stato nell'interfaccia in modo da consentire ad un decoratore di arricchire la logica di transizione dello stato.

Il mio disegno espone l'accesso allo stato?

Facciamo un esempio e supponiamo di avere questa interfaccia:

public interface ILoginService
{
    void Login();
    void Logout();
    bool IsLoggedIn { get; } // <-- should that be exposed?
}

Un'implementazione potrebbe essere simile a questa:

public class LoginService : ILoginService
{
    private bool _loggedIn;
    public IsLoggedIn => _loggedIn;

    public void Login()
    {
        if (!IsLoggedIn) // <-- should that be done?
        { ... }          // Login procedure
    }   
    public void Logout() { ... }   
}

Quale sarebbe l'approccio giusto?

  • Variante A

    Esporre la proprietà IsLoggedIn tramite l'interfaccia, ma non controllare IsLoggedIn prima della procedura di accesso all'interno dell'implementazione. Motivo: se espongo la proprietà, mi aspetto che il client chiamante la gestisca correttamente. Un decoratore o un cliente può forzare la procedura di accesso ignorando la proprietà IsLoggedIn (a causa della conoscenza avanzata / migliore). Svantaggio: posso aspettarmi dagli implementatori che non debbano controllare la proprietà IsLoggedIn internamente?

  • Variante B

    Esporre la proprietà IsLoggedIn tramite l'interfaccia e controllare IsLoggedIn prima della procedura di accesso all'interno dell'implementazione. Motivo: posso solo tornare nella funzione e quindi evitare le eccezioni con "double login". Svantaggio: un cliente o un decoratore (con conoscenze avanzate) non può forzare l'implementazione a effettuare nuovamente il login, perché controlla sempre lo stato stesso

  • Variante C

    Non esporre la proprietà IsLoggedIn tramite l'interfaccia, ma controlla IsLoggedIn prima della procedura di accesso all'interno dell'implementazione. Motivo: il cliente non deve preoccuparsi dello stato e può fidarsi dell'implementazione di non "doppio login". Svantaggio: un decoratore non può decorare la proprietà IsLoggedIn o forzare l'implementazione a riconnettersi (perché il decoratore potrebbe avere una certa conoscenza che il login è stato rifiutato / cancellato qualche tempo dopo in background)

Problema pratico alla base di questa domanda: come arricchire i cambiamenti di stato?

In alcuni dei nostri sistemi, il login viene scartato se viene eseguito un reset dell'hardware. Volevo gestire questo tramite un decoratore che è in grado di rilevare questi ripristini al fine di rispettare l'SRP.

Ma per farlo funzionare, il decoratore deve essere in grado di forzare l'accesso, anche se lo stato interno dell'implementazione dice che è già stato effettuato il login. Quindi andrei per la variante A, ma non sono sicuro se posso aspettarmi di non controllare lo stato internamente dagli sviluppatori.

Aggiornamento - La mia idea di un decoratore

public class ResetAwareDecorator : ILoginService
{
    private readonly ILoginService _decoratee;
    private readonly IResetDetector _reset;
    private bool _newResetAfterLastLogin;
    public ResetAwareDecorator(ILoginService decoratee, IResetDetector reset) 
    { ... }

    public bool IsLoggedIn => _decoratee.IsLoggedIn && !_newResetAfterLastLogin; // I would expect the client to test this before calling Login();

    public void Login()
    {
        _decoratee.Login(); // I would expect the decoratee to execute Login() independent of the state _decoratee.IsLoggedIn
        _newResetAfterLastLogin = false;
    }
    public void Logout() { ... }   
}

Le mie aspettative sono valide in questo scenario?

    
posta Creepin 14.09.2018 - 21:53
fonte

2 risposte

0

Le tue varianti sono adatte per il lavoro?

La variante A non sembra essere una buona idea: trasferisci una responsabilità interna all'esterno. Questo rompe il principio della minima conoscenza.

La variante B sembra perfettamente soddisfacente: è legittimo che il mondo esterno possa interrogare lo stato di un oggetto se è più di un dettaglio di implementazione (ad esempio se qualche interfaccia utente potrebbe dover visualizzare lo stato della connessione).

La variante C sembra ok ma non soddisfa i tuoi bisogni.

È una buona idea usare un decoratore per questo scopo?

L'intento del modello decoratore è di aggiungere una nuova responsabilità dinamicamente a un oggetto. Quindi è comprensibile che tu tenti di aggiungere la funzione di reset con questa media.

Sfortunatamente, non solo aggiungerai una nuova responsabilità, ma modificheresti anche il comportamento dell'oggetto. Quindi potresti rompere il principio di sostituzione di Liskov . Certo, dipende dalla garanzia che offri per ILoginService , ma mi sembra che:

  • Non sembri rafforzare le precondizioni né modificare gli invarianti
  • Potresti tuttavia indebolire le post-condizioni (a seconda di come le definisci per l'interfaccia e i casi di test).
  • Sicuramente romperai il vincolo della cronologia perché permetti un cambio di stato che non è previsto nel super-tipo.

Alternativa

Vista la limitazione della cronologia (LSP) e considerando che un accesso può essere interrotto da qualche evento in alcune implementazioni di ILoginService , è chiaro che il design della classe base dovrebbe supportare una potenziale interruzione:

  • ILoginService potrebbe prevedere un metodo .ForcedLogout . Per i servizi di accesso che non lo consentono, è possibile creare una specializzazione del metodo che attiva un'eccezione.
  • ILoginService potrebbe essere un osservatore che potrebbe sottoscrivere un gestore di eventi che notifica eventi che possono influire sullo stato di accesso. La tua classe base avrebbe un metodo update() vuoto, mentre quello specializzato potrebbe consentire al suo update() di accedere allo stato protetto o alle funzioni di ripristino protetto.

IMHO, il decoratore può fare ciò di cui hai bisogno, ma nel tuo caso è solo un work-around di un design imperfetto. Quindi consiglierei di andare per le alternative. Il secondo è un po 'più complesso, ma ha il vantaggio di impedire l'invocazione diretta del reset forzato per le classi che non dovrebbero consentirlo.

    
risposta data 15.09.2018 - 00:39
fonte
1

Questo è un po 'più ingenuo, ma quello che vedo è che il tuo LoginService in realtà non sa se è loggato o meno. Semplicemente sa come login e logout e va bene. Quindi abbiamo solo bisogno di qualcuno che sappia se è loggato o meno. Diciamo che qualcuno implementa ILoginState e lo si inietta (vale a dire nel costruttore forse?) Del LoginService , quindi quando controlla IsLoggedIn chiama ILoginState . Quindi puoi implementare ILoginState come osservatore, parte del decoratore (che aggiungerebbe solo questa responsabilità) o qualsiasi altra cosa.

    
risposta data 15.09.2018 - 23:42
fonte