Dipendenza circolare. Come sistemarlo

1

Ho una classe IItemProcessor che preleva ogni elemento da un elenco e lo invia a un'API Web ( IApiService ). L'elaborazione viene eseguita in modo asincrono su un altro thread.

Se l'API web risponde con "utente non autorizzato", l'app deve uscire. Per questo, ho un IApp che ha il metodo LogoutUser() .

Attualmente l'implementazione ItemProcessor rileva errori generati da IApiService e se l'errore è "non autorizzato", chiama IApp:LogoutUser .

Il problema è nell'implementazione di IApp:LogoutUser Devo chiamare IItemProcessor:Stop per interrompere l'elaborazione. Il che significa che esiste una dipendenza circolare tra App e 'ItemProcessor.

Sto usando Dependency Injection nel c-tor e questo mi dà una costruzione ricorsiva infinita degli oggetti concreti:

public class App : IApp 
{
   public App(IItemProcessor itemProcessor)
   {
    ...
   }

   public void Logout() 
   {
       _itemProcessor.Stop(); 
   }
}

public class ItemProcessor : IItemProcessor 
{
    public ItemProcessor (IApp app)
    {
      ...
    }

    public void Stop() {
      ...
    }

    void Run() 
    {
        ... for each item in list ..
         try
         {
            ... try send item ....
         }
         catch(UserNotAuthorizedException)
         {
             _app.Logout();
         }
    }
}

Come posso risolvere questo problema?

IApp deve sapere su IItemProcessor per avviarlo e interromperlo su login utente e logout. Ma mi sento come se IItemProcessor interrompesse SRP, in realtà non dovrebbe chiamare Logout , ma piuttosto in qualche modo informare "qualcuno" dell'errore, e che "qualcuno" ha a sua volta la responsabilità di chiamare IApp:LogoutUser . Ma cosa dovrebbe essere questo "qualcuno"? Dovrebbe essere un ascoltatore per gli errori del processore dell'articolo?

Ho difficoltà a capire come dividere le responsabilità.

Ho provato a utilizzare lo schema di comando avendo LogoutCommand

public class LogoutCommand
{
    readonly IApp _app;

    public LogoutCommand(IApp app)
    {
        _app = app;
    }

    public void Execute()
    {
        _app.UserLogout();
    }
}

l'implementazione ItemProcessor potrebbe chiamare logoutCommand.Execute() . Ciò disaccoppia il ItemProcessor dalla conoscenza di App , ma ho lo stesso problema perché quando App c-tor chiama ItemProcessor c-tor che chiama LogoutCommand che chiama App c-tor.

Un'altra (cattiva) idea: pensavo che una fabbrica potesse alleviare questo problema. Ho pensato di avere un ILogoutCommandFactory factory che crea LogoutCommand . Finché non ho creato LogoutCommand nel c-tor di ItemProcessor , ho pensato che potesse funzionare. Ma il fatto che io non possa farlo, mi sembra l'odore del codice.

A me DI in c-tor sembra iniziare come un problema.

    
posta Daniel 02.05.2017 - 20:36
fonte

1 risposta

4

Tecnicamente, non c'è dipendenza ciclica. Quando ho capito bene, App dipende dall'interfaccia IItemProcessor , e non dalla sua implementazione ItemProcessor . Inoltre, ItemProcessor dipende da IApp e non da App . Tuttavia, dovrai passare all'iniezione di proprietà per almeno una delle classi, questo sarà simile a

public class App : IApp 
{
   public App(IItemProcessor itemProcessor)
   {
      itemProcessor.TheApp = this;
   }
}

public class ItemProcessor : IItemProcessor 
{
    public ItemProcessor ()
    {
      ...
    }

    public IApp TheApp{get;set;}

}

Tuttavia, per separare ancora di più i tuoi componenti, o se il tuo framework DI ha ancora problemi con questo, IApp potrebbe fornire un meccanismo editore / sottoscrittore (AKA modello di osservatore ) per qualcosa come un evento" BeforeLogout ". In modo simile, IItemProcessor potrebbe fornire un meccanismo editore / sottoscrittore per gli eventi "non autorizzati" che ottiene da IApiService . Ad esempio, in C # questo potrebbe apparire come

interface IItemProcessor
{
    void SubscribeToUserNotAuthorized(Action<Exception> observer);
}

e

public ItemProcessor() : IItemProcessor
{
    List<Action<Exception>> notAuthorizedObservers;

    public void SubscribeToUserNotAuthorized(Action<Exception> observer)
    {
        notAuthorizedObservers.Add(observer);
    }

    // and somewhere below

    // ...
         catch(NotAuthorizedException ex)
         {
            foreach(var observer in notAuthorizedObservers)
               observer(ex);
         }
}
    
risposta data 02.05.2017 - 20:49
fonte