2015-02-25 20 views
10

Saluto a tutti i guru Java!Registrazione dai metodi di interfaccia predefiniti

Poiché Java8 possiamo avere implementazioni predefinite nelle interfacce (yay!). Tuttavia, il problema sorge quando si desidera effettuare il log dal metodo predefinito.

Ho la sensazione che non sia saggio chiamare .getLogger() ogni volta che voglio registrare qualcosa in un metodo predefinito.

Sì, è possibile definire una variabile statica in un'interfaccia, ma non è comunque una buona pratica per le interfacce + espone il registratore (deve essere pubblico).

soluzione che ho in questo momento:

interface WithTimeout<Action> { 

    default void onTimeout(Action timedOutAction) { 
     LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction); 
    } 

    static final class LogHolder { 
     private static final Logger LOGGER = getLogger(WithTimeout.class); 
    } 
} 

LogHolder è ancora visibile a tutti che in realtà non ha alcun senso in quanto non prevede alcun metodo e dovrebbe essere interno all'interfaccia.

Qualcuno di voi sa di una soluzione migliore? :)

EDIT: Io uso SLF4J sostenuta da Logback

+0

"Ho la sensazione che non è saggio chiamare .getLogger() ogni volta che voglio per accedere qualcosa in un metodo di default ". Hai eseguito misurazioni delle prestazioni per sostenere questa sensazione? In caso contrario, non ottimizzare le prestazioni se non è necessario. – Ray

+1

L'esposizione di una classe pubblica 'LogHolder' o di un' Logger LOGGER' pubblico non fa molta differenza secondo me. – assylias

+1

Non vedo alcun motivo per _yay! _ –

risposta

7

Se non si vuole esporre la classe LogHolder al pubblico, non rendono una classe membro del interface. Non vi è alcun vantaggio nel farne una classe membro, non si risparmia nemmeno la digitazione in quanto è necessario qualificare l'accesso al campo con il nome della classe titolare, indipendentemente dal fatto che si tratti di una classe membro o di una classe all'interno dello stesso pacchetto :

public interface WithTimeout<Action> { 

    default void onTimeout(Action timedOutAction) { 
     LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction); 
    } 
} 
final class LogHolder { // not public 
    static final Logger LOGGER = getLogger(WithTimeout.class); 
} 
+0

Sì, ma questo non impedisce agli altri membri dello stesso pacchetto di accedere al logger stesso. Forse sono un purista troppo. Un programmatore decente difficilmente userebbe il logger di una classe diversa e non lascerebbe passare una revisione del codice, immagino. –

+0

Poiché non c'è modo di avere membri 'private' per' interface's, non c'è modo di aggirarlo. Ma anche se esistesse un modo per rendere 'LogHolder' una classe' private', ciò non impediva ad altre classi di chiamare 'getLogger (WithTimeout.class)' per ottenere la stessa istanza di logger, quindi non ne vale la pena. È utile spostare la classe fuori dall'interfaccia per evitare di inquinare l'API con gli artefatti di implementazione, ma i logger non dovrebbero essere rilevanti per la sicurezza. – Holger

+0

sì, hai ragione - grazie per la discussione :) –

0

Ecco qui.

Il logger è privato all'interfaccia. Nessuno oltre a questa interfaccia e i suoi metodi predefiniti possono accedere a qualsiasi cosa all'interno di Test2. E nulla può estendere la classe Test2.

Nessuno ti sta suggerendo di farlo ... ma funziona! Ed è un ottimo modo per accedere a un logger di classe per l'interfaccia principale, e possibilmente un modo intelligente per fare qualcos'altro non del tutto pazzo.

Questo è esattamente lo stesso di LogHolder nella domanda OP, tranne che la classe Test2 è anche tutti i metodi privati ​​e privati ​​del costruttore e la classe non è contrassegnata come statica.

E come bonus aggiuntivo, mantiene lo stato, staticamente e per istanza. (non farlo in un programma vero e proprio, per favore!)

public class TestRunner { 
    public static void main(String[] args) { 
     Test test = new Test() { 
     }; 
     test.sayHello("Jane"); 
     System.out.println("Again"); 
     test.sayHello("Bob"); 
    } 
} 
public interface Test { 
    default void sayHello(String name) { 
     Logger log = Test2.log; 
     Test2 ref = Test2.getMine.apply(this); 
     int times = ref.getTimes(); 
     for (int i = 0; i < times; i++) { 
      System.out.println(i + ": Hello " + name); 
     } 
     log.info("just said hello {} times :)",times); 
    } 
    final class Test2 { 
     private static final Logger log = LoggerFactory.getLogger(Test.class); 
     private static final Map lookup = new WeakHashMap(); 
     private static final Function getMine = (obj) -> { 
      return lookup.computeIfAbsent(obj, (it) -> new Test2()); 
     }; 
     private int calls = 0; 
     private Test2() { 
     } 
     private void setCalls(int calls) { 
      this.calls = calls; 
     } 
     private int getCalls() {return calls;} 
     private int getTimes() {return ++calls;} 
    } 
}

+1

Hai sostanzialmente cambiato la soluzione, l'OP aveva già all'apertura di questa domanda, aggiungendo un sacco di cose, l'OP non ha chiesto. La classe 'Test2' ha ancora visibilità' public' ed è ancora 'statica' se la dichiari esplicitamente o meno. Aggiungere un costruttore 'private' è un piccolo miglioramento, ma non cambia il fatto che la classe nel suo insieme sia visibile nell'API dell'interfaccia' Test'. Non vi è alcun motivo per riportare l'istanza scoraggiata alla mappatura dei dati, poiché l'OP non ha mai chiesto di associare i dati alle istanze. È valsa davvero la pena di scavare una domanda vecchia di un anno e mezzo? – Holger

+0

Sto solo dimostrando cosa * può * essere fatto, che ti piaccia o no. Oltre alla visibilità della misteriosa classe helper di LogHolder, non c'è nulla di sbagliato nella soluzione. Penso che molte persone apprezzeranno vedere le scelte che hanno. La necessità di avere un logger in un metodo predefinito è reale e questo (e gli OP) fornisce un modo per farlo. Questo semplicemente fa in modo che LogHolder non sia estensibile o istanziabile. –

Problemi correlati