Faz sentido dividir uma interface multi-método existente em várias interfaces de método único apenas para aproveitar lambdas?

5

Digamos que eu tenha uma interface de retorno de chamada existente que tenha vários métodos. Para ilustrar o meu ponto, eu uso um callback dos gostos que você veria no código que executa algumas operações do cliente 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 um método que faz uma solicitação levaria uma instância desse retorno de chamada como um argumento:

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

Como é óbvio, isso sofre com verbosidade no site da chamada:

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*/}

    });
}

Eu tenho algo semelhante no meu código e está funcionando bem. Eu uso uma classe DefaultGetCallback que fornece a implementação padrão dos métodos onAuthFailure e onError , pois a ação que eu quero executar nesses casos é praticamente a mesma, independentemente do recurso que estou solicitando.

A questão é, faz sentido refatorar isso em uma classe composta de um conjunto de interfaces de método único para aproveitar a sintaxe 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;
    }

}

Eu poderia usar um padrão Builder para construir o GetCallback , mas isso é além do ponto. O site de chamadas agora se torna:

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

Uma vantagem é que eu posso personalizar o comportamento de "eventos" individuais (sucesso, erro, etc.) isoladamente, no site de chamada, em vez de ter que criar uma subclasse ou criar uma classe interna anônima.

Mas então, quanto é demais? Especialmente considerando o fato de que meu design atual já me serviu bem?

    
por curioustechizen 16.09.2014 / 10:51
fonte

3 respostas

4

Em vez de construir uma fachada , não faria sentido fazer algo mais assim?

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.
        //}
    });
}

Ele ainda é compatível com sua API anterior e, internamente, quem se importa com o fato de o mesmo objeto ser um ouvinte para todos os três eventos. Também abre as possibilidades de adicionar implementações padrão e permitir a chance de emitir uma solicitação com apenas SuccessListener .

Observação: alterei o código para usar default para ter ainda mais gentileza.

    
por 22.09.2014 / 17:38
fonte
1

Eu diria que, se houver um custo para os seus clientes (os usuários da sua interface) migrarem para essa outra API, não faça isso, não é tão feio assim. Se é algo novo, ou o custo da mudança é muito baixo (poucos clientes), vá em frente, pois seria muito melhor para a legibilidade.

Outro ponto é: como escrever interfaces fluentes, é extremamente detalhado escrever essas interfaces funcionais para poder usar o lambdas, portanto, se esta for uma API interna, eu usaria a implementação simples e direta (sem lambdas). Por outro lado, se esta é uma biblioteca que será usada por outros sistemas, eu iria com o caminho lambda. :)

    
por 22.09.2014 / 15:40
fonte
1

Você não deve projetar interfaces com base nos recursos da linguagem de implementação, mas sim no modelo de negócios e nos princípios de design de OOP.

Um motivo para dividir sua interface seria uma violação do Princípio de segregação de interface (I da SOLID). Isso significa que faz sentido dividir se suas implementações de GetCallback normalmente não precisam implementar todos os métodos.

    
por 22.09.2014 / 16:32
fonte