Ha senso suddividere un'interfaccia multi-metodo esistente in diverse interfacce a singolo metodo solo per approfittare di lambda?

5

Supponiamo di avere un'interfaccia di callback esistente con più metodi. Per illustrare il mio punto, utilizzo una richiamata che appaia nel codice che esegue alcune operazioni client HTTP:

public interface GetCallback<T> {
    public void onSuccess(T data);
    public void onAuthFailure();
    public void onError(RequestError error);

    //Potentially more methods like onPreGet etc...
}

E un metodo che effettua una richiesta richiederebbe un'istanza di questo callback come argomento:

public void issueRequest(String url, GetCallback<T> callback) {
    // Implementation ...
}

Come ovvio, questo soffre di verbosità nel sito di chiamata:

public void getData(){
    issueRequest("http://programmers.stackexchange.com", new GetCallback<String>(){
        public void onSuccess(String data) {/*Do Something*/}
        public void onAuthFailure(){/*Ask for credentials*/}
        public void onError(RequestError error){/*Show error message*/}

    });
}

Ho qualcosa di simile nel mio codice e ha funzionato bene. Uso una classe DefaultGetCallback che fornisce l'implementazione predefinita dei metodi onAuthFailure e onError poiché l'azione che voglio eseguire in questi casi è praticamente la stessa indipendentemente dalla risorsa che sto richiedendo.

La domanda è, ha senso rifattorizzare questo in una classe composta da un insieme di interfacce a metodo singolo per trarre vantaggio dalla sintassi lambda?

public class GetCallback<T>{
    public interface OnSuccessListener<T> {
        public void onSuccess(T data);
    }

    public interface OnAuthFailureListener {
        public void onAuthFailure();
    }

    public interface OnErrorListener {
        public void onError(RequestError error);
    }

    private OnSuccessListener mSuccess;
    private OnAuthFailureListener mAuthFailure;
    private OnErrorListener mError;

    public GetCallback<T>(OnSuccessListener<T> success) {
        this.mSuccess = success;
    }

    public GetCallback<T> withAuthFailure(OnAuthFailureListener authFailure){
        this.mAuthFailure = authFailure;
        return this;
    }

    public GetCallback<T> withError(OnErrorListener error){
        this.mError = error;
        return this;
    }

}

Potrei usare un pattern Builder per costruire il GetCallback qui, ma questo è oltre il punto. Il sito di chiamata ora diventa:

public void getData(){
    issueRequest(
        "http://programmers.stackexchange.com", 
        new GetCallback<String>(s -> doSomething(s))
            .withAuthFailure(() -> askForCredentials())
            .withError(error -> showErrorMessage(error))
    );
}

Un vantaggio è che posso personalizzare il comportamento dei singoli "eventi" (successo, errore ecc.) in isolamento, sul sito di chiamata, piuttosto che dover sottoclasse o creare una classe interna anonima.

Ma allora, quanto è troppo? Soprattutto dato che il mio attuale design mi ha già servito bene?

    
posta curioustechizen 16.09.2014 - 10:51
fonte

3 risposte

4

Invece di costruire una facciata non avrebbe senso fare qualcosa di più simile a questo?

public interface SuccessListener<T> {

    default void onSuccess(T data) {
        // Completely ignore it.
    }

}

public interface AuthFailureListener {

    default void onAuthFailure() {
        // Completely ignore it.
    }

}

public interface ErrorListener {

    default void onError() {
        // Completely ignore it.
    }

}

// Retain the old API
public interface GetCallback<T> extends SuccessListener<T>, AuthFailureListener, ErrorListener {
    // Now empty - we are a conglmerate.
}

public static <T> void issueRequest(String url, SuccessListener<T> successListener, AuthFailureListener failureListener, ErrorListener errorListener) {
    // Implementation ...
}

public static <T> void issueRequest(String url, GetCallback<T> callback) {
    // Ensures backwards compatability.
    issueRequest(url, callback, callback, callback);
}

public void getData() {
    issueRequest("http://programmers.stackexchange.com", new GetCallback<String>() {

        @Override
        public void onSuccess(String data) {
        }

        @Override
        public void onAuthFailure() {
        }

        //@Override
        //public void onError() {
        // Can now leave out the ones you don't want.
        //}
    });
}

È ancora compatibile con la tua API precedente e internamente a chi importa che lo stesso oggetto sia un ascoltatore per tutti e tre gli eventi. Apre anche la possibilità di aggiungere implementazioni predefinite e di consentire la possibilità di inviare una richiesta con solo SuccessListener .

Nota: ho ottimizzato il codice per utilizzare default per ancora più gradevolezza.

    
risposta data 22.09.2014 - 17:38
fonte
1

Direi che se c'è un costo per i tuoi clienti (gli utenti della tua interfaccia) per migrare a questa altra API, non farlo, non è poi così brutto. Se è qualcosa di nuovo, o il costo del cambiamento è molto basso (pochi client), provaci, perché sarebbe molto meglio per la leggibilità.

Un altro punto è: come scrivere interfacce fluenti, è estremamente prolisso scrivere quelle interfacce funzionali per poter utilizzare i lambda, quindi se si tratta di un'API interna, utilizzerei l'implementazione semplificata in avanti (senza lambdas). D'altra parte, se questa è una libreria che verrà utilizzata per un altro sistema, andrei con il percorso lambda. :)

    
risposta data 22.09.2014 - 15:40
fonte
1

Non dovresti progettare interfacce basate sulle caratteristiche del linguaggio di implementazione, ma piuttosto sul modello di business e sui principi di progettazione OOP.

Un motivo per suddividere la tua interfaccia sarebbe una violazione del principio di segregazione dell'interfaccia (I di SOLID). Ciò significa che ha senso dividere se le implementazioni GetCallback di solito non hanno bisogno di implementare tutti i metodi.

    
risposta data 22.09.2014 - 16:32
fonte

Leggi altre domande sui tag