10

Sto cercando di trovare un modo piacevole per implementare un servizio che si basa su una classe di libreria di terze parti. Ho anche un'implementazione "predefinita" da utilizzare come fallback nel caso in cui la libreria non sia disponibile o non sia in grado di fornire una risposta.Modello di fallback Java

public interface Service { 

    public Object compute1(); 

    public Object compute2(); 
} 

public class DefaultService implements Service { 

    @Override 
    public Object compute1() { 
     // ... 
    } 

    @Override 
    public Object compute2() { 
     // ... 
    } 
} 

L'attuale implementazione del servizio sarebbe qualcosa di simile:

public class ServiceImpl implements Service { 
    Service defaultService = new DefaultService(); 
    ThirdPartyService thirdPartyService = new ThirdPartyService(); 

    @Override 
    public Object compute1() { 
     try { 
      Object obj = thirdPartyService.customCompute1(); 
      return obj != null ? obj : defaultService.compute1(); 
     } 
     catch (Exception e) { 
      return defaultService.compute1(); 
     } 
    } 

    @Override 
    public Object compute2() { 
     try { 
      Object obj = thirdPartyService.customCompute2(); 
      return obj != null ? obj : defaultService.compute2(); 
     } 
     catch (Exception e) { 
      return defaultService.compute2(); 
     } 
    } 
} 

L'implementazione attuale sembra duplicare le cose un po 'nel modo in cui solo le chiamate effettive dei servizi sono diversi, ma la try/catch e il meccanismo predefinito è praticamente lo stesso. Inoltre, se nel servizio è stato aggiunto un altro metodo, l'implementazione sembrerebbe quasi uguale.

C'è un motivo di progettazione che potrebbe essere applicato qui (proxy, strategy) per rendere il codice un aspetto migliore e rendere ulteriori aggiunte meno copia-incolla?

+0

Cosa versione di Java stai usando? – Radiodef

risposta

3

è possibile estrarre la logica comune in un metodo separato con il metodo riferimenti, come:

public class ServiceImpl implements Service { 
    Service defaultService = new DefaultService(); 
    ThirdPartyService thirdPartyService = new ThirdPartyService(); 

    @Override 
    public Object compute1() { 
     return run(thirdPartyService::customCompute1, defaultService::compute1); 
    } 

    @Override 
    public Object compute2() { 
     return run(thirdPartyService::customCompute2, defaultService::compute2); 
    } 

    private static <T> T run(Supplier<T> action, Supplier<T> fallback) { 
     try { 
      T result = action.get(); 
      return result != null ? result : fallback.get(); 
     } catch(Exception e) { 
      return fallback.get(); 
     } 
    } 
} 
+1

Dubito che questa soluzione possa essere applicata correttamente se i metodi accettano argomenti. Dovresti sovraccaricare 'run' per almeno' Function' e 'BiFunction', e quindi potresti aver bisogno di un' 'TriFunction'] (http://stackoverflow.com/questions/18400210/java-8-where-is -trifunction-and-kin-in-java-util-function-or-what-is-the-alt) ma non scala molto bene. Inoltre, ogni nuovo metodo di 'Service' richiede un'implementazione corrispondente in' ServiceImpl', che non si adatta bene e potrebbe anche essere una fonte di bug e problemi di manutenzione. –

+0

Concordato con @Didier, mentre questa è una soluzione per questo caso specifico, non è una buona soluzione generale. Le maniglie del metodo sono fantastiche, ma probabilmente non sono adatte qui. – Guillaume

2

Una delle migliori librerie per questo è Netflix 'Hystrix. Non sono sicuro che tu abbia bisogno di un sollevamento così pesante. Fornirà pool di thread, timeout, fallback, monitoraggio, modifiche alla configurazione di runtime, cortocircuito, ecc.

Fondamentalmente si tratta di una libreria che difende il proprio codice dai guasti delle sue dipendenze.

+0

Hystrix sembra fantastico! Lo sto valutando proprio ora. Fornisce molto di più di quello che l'OP chiedeva, ma questa può essere una buona cosa ... – Guillaume

+0

Grazie per aver menzionato Hystrix. Non ne ero a conoscenza, ma ha molto più di quello che mi serve e abbiamo una politica piuttosto rigida sull'aggiunta di libs. Verificherò però – user4132657

3

Un proxy può probabilmente aiutarti qui. L'esempio che segue non è testato, ma dovrebbe darvi un'idea di ciò che si potrebbe mettere in atto:

public class FallbackService implements InvocationHandler { 

    private final Service primaryService; 
    private final Service fallbackService; 

    private FallbackService(Service primaryService, Service fallbackService) { 
     this.primaryService = primaryService; 
     this.fallbackService = fallbackService; 
    } 

    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
     try { 
      Object result = method.invoke(primaryService, args); 
      if (result != null) return result; 
     } catch (Exception ignore) {} 
     return method.invoke(fallbackService, args); 
    } 

    public static Service createFallbackService(Service primaryService, Service fallbackService) { 
     return (Service) Proxy.newProxyInstance(
       Service.class.getClassLoader(), 
       new Class[] { Service.class }, 
       new FallbackService(primaryService, fallbackService) 
     ); 
    } 
}