Existe uma maneira elegante de fazer com que todos os métodos de uma classe comecem com um determinado bloco de código?

143

Eu tenho uma classe onde todo método começa da mesma maneira:

class Foo {
  public void bar() {
    if (!fooIsEnabled) return;
    //...
  }
  public void baz() {
    if (!fooIsEnabled) return;
    //...
  }
  public void bat() {
    if (!fooIsEnabled) return;
    //...
  }
}

Existe uma boa maneira de exigir (e espero que não escreva toda vez) a fooIsEnabledparte para todos os métodos públicos da classe?

Cristina
fonte
45
Analise a Programação Orientada a Aspectos (especificamente antes dos conselhos ).
Sotirios Delimanolis
15
Para quantos métodos você precisa usar este padrão? Antes de seguir o caminho da introdução da AOP, você pode considerar apenas ter esse pouco de código repetido. Às vezes, copiar e colar um pouco é a solução mais simples.
bhspencer
51
Eu suspeito que seu futuro mantenedor ficaria mais feliz com a placa extra da caldeira do que ter que aprender uma estrutura de AOP.
bhspencer
7
Se todo método da classe precisa fazer a mesma coisa em suas primeiras linhas de código, temos um design ruim.
Tulains Córdova
7
@ user1598390: A questão não está fora de tópico aqui, e não há nada no escopo dos programadores que torne a questão especialmente significativa lá.
Robert Harvey

Respostas:

90

Eu não sei sobre o elegante, mas aqui está uma implementação de trabalho usando o Java embutido java.lang.reflect.Proxyque impõe que todas as invocações de métodos emFoo começam verificando o enabledestado.

main método:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

Foo interface:

public interface Foo {
    boolean getEnabled();
    void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory classe:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {
        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class &&
                !method.getName().equals("getEnabled") &&
                !method.getName().equals("setEnabled")) {

                if (!this.fooImpl.getEnabled()) {
                    return null;
                }
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

Como outros já apontaram, parece um exagero para o que você precisa se você tiver apenas alguns métodos para se preocupar.

Dito isto, certamente existem benefícios:

  • Uma certa separação de preocupações é alcançada, porque Fooas implementações do método não precisam se preocupar com a preocupação enabledtransversal da verificação. Em vez disso, o código do método precisa se preocupar apenas com o objetivo principal do método, nada mais.
  • Não há como um desenvolvedor inocente adicionar um novo método à Fooclasse e, por engano, "esquecer" de adicionar a enabledverificação. O enabledcomportamento da verificação é herdado automaticamente por qualquer método adicionado recentemente.
  • Se você precisar adicionar outra preocupação transversal ou melhorar a enabledverificação, é muito fácil fazê-lo com segurança e em um só lugar.
  • É bom que você possa obter esse comportamento semelhante ao AOP com a funcionalidade Java integrada. Você não é obrigado a integrar outra estrutura comoSpring , embora elas também possam ser boas opções.

Para ser justo, algumas das desvantagens são:

  • Parte do código de implementação que lida com as invocações de proxy é feio. Alguns diriam também que ter classes internas para impedir a instanciação doFooImpl classe é feio.
  • Se você deseja adicionar um novo método ao Foo , é necessário fazer uma alteração em 2 pontos: a classe de implementação e a interface. Não é grande coisa, mas ainda é um pouco mais de trabalho.
  • As invocações de proxy não são gratuitas. Há uma certa sobrecarga de desempenho. Para uso geral, porém, não será perceptível. Veja aqui para mais informações.

EDITAR:

O comentário de Fabian Streitel me fez pensar em 2 aborrecimentos com a minha solução acima que, admito, não estou feliz comigo mesma:

  1. O manipulador de chamada usa seqüências de caracteres mágicas para ignorar a "verificação ativada" nos métodos "getEnabled" e "setEnabled". Isso pode ser interrompido facilmente se os nomes dos métodos forem refatorados.
  2. Se houver um caso em que novos métodos precisem ser adicionados que não herdem o comportamento de "verificação habilitada", pode ser bem fácil para o desenvolvedor cometer um erro e, no mínimo, isso significaria adicionar mais magia cordas.

Para resolver o ponto 1 e, pelo menos, aliviar o problema com o ponto 2, eu criaria uma anotação BypassCheck(ou algo semelhante) que poderia ser usada para marcar os métodos na Foointerface para os quais não quero executar o " verificação ativada ". Dessa forma, não preciso de seqüências de caracteres mágicas e fica muito mais fácil para um desenvolvedor adicionar corretamente um novo método neste caso especial.

Usando a solução de anotação, o código ficaria assim:

main método:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

BypassCheck anotação:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}

Foo interface:

public interface Foo {
    @BypassCheck boolean getEnabled();
    @BypassCheck void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory classe:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {

        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class
                    && !method.isAnnotationPresent(BypassCheck.class) // no magic strings
                    && !this.fooImpl.getEnabled()) {

                return null;
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}
sstan
fonte
11
Entendo que esta é uma solução inteligente, mas você realmente usaria isso?
bhspencer
1
é uma boa solução alternativa, você está usando o padrão de proxy dinâmico para decorar o objeto com esse comportamento comum encontrado no início de cada método.
Victor
11
@ bhspencer: Uma pergunta muito legítima. Na verdade, eu o usei muitas vezes para manipular exceções, registrar, manipular transações, etc. Admito que, para classes menores, parece um exagero e pode muito bem ser. Mas se eu espero que a classe cresça muito em complexidade e queira garantir um comportamento consistente em todos os métodos ao fazer isso, não me importo com esta solução.
sstan
1
Não fazer parte dos 97% do mal aqui, mas quais são as implicações de desempenho dessa classe de proxy?
precisa saber é o seguinte
5
@corsiKa: Boa pergunta. Não há dúvida de que o uso de proxies dinâmicos é mais lento que as chamadas de método diretas. Para uso geral, porém, a sobrecarga de desempenho será imperceptível. Segmento SO relacionada se você estiver interessado: custo de desempenho de Java proxy dinâmico
sstan
51

Há muitas boas sugestões. O que você pode fazer para resolver o seu problema é pensar no Padrão do Estado e implementá-lo.

Dê uma olhada neste snippet de código. Talvez você tenha uma idéia. Nesse cenário, parece que você deseja modificar toda a implementação de métodos com base no estado interno do objeto. Lembre-se de que a soma dos métodos em um objeto é conhecida como comportamento.

public class Foo {

      private FooBehaviour currentBehaviour = new FooEnabledBehaviour (); // or disabled, or use a static factory method for getting the default behaviour

      public void bar() {
        currentBehaviour.bar();
      }
      public void baz() {
        currentBehaviour.baz();
      }
      public void bat() {
        currentBehaviour.bat();
      }

      public void setFooEnabled (boolean fooEnabled) { // when you set fooEnabel, you are changing at runtime what implementation will be called.
        if (fooEnabled) {
          currentBehaviour = new FooEnabledBehaviour ();
        } else {
          currentBehaviour = new FooDisabledBehaviour ();
        }
      }

      private interface FooBehaviour {
        public void bar();
        public void baz();
        public void bat();
      }

      // RENEMBER THAT instance method of inner classes can refer directly to instance members defined in its enclosing class
      private class FooEnabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is enabled
        }
        public void baz() {}
        public void bat() {}

      }

      private class FooDisabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is desibled
        }
        public void baz() {}
        public void bat() {}

      }
}

Espero que você goste!

PD: É uma implementação do Padrão de Estado (também conhecido como Estratégia, dependendo do contexto .. mas os princípios são os mesmos).

Vencedor
fonte
1
O OP não precisa repetir a mesma linha de código no início de cada método e sua solução envolve repetir a mesma linha de código no início de cada método.
Tulains Córdova
2
@ user1598390 não há necessidade de repetir a avaliação; dentro do FooEnabledBehaviour, você está assumindo que o cliente deste objeto configurou fooEnabled como true, portanto, não há necessidade de mastigar. O mesmo vale para a classe FooDisabledBehaviour. Verifique novamente, codifique-o.
Victor
2
Obrigado @ bayou.io, vamos esperar até a resposta do OP. Eu acho que a comunidade está fazendo um ótimo trabalho aqui, há muitas boas dicas por aqui!
Victor
2
De acordo com @dyesdyes, não consigo imaginar implementá-lo para nada além de uma classe realmente trivial. É muito problemático, considerando que bar()in FooEnabledBehaviore bar()in FooDisabledBehaviorpodem compartilhar muito do mesmo código, com talvez até uma única linha diferente entre os dois. Você poderia facilmente, especialmente se esse código fosse mantido por desenvolvedores juniores (como eu), acabar com uma bagunça gigante de porcaria que é impossível de manter e não-testável. Isso pode acontecer com qualquer código, mas isso parece tão fácil de estragar muito rápido. +1, porque boa sugestão.
Chris Cirefice
1
Mmmmm ... eu não sei ... mas primeiro, obrigado pelos comentários. Para mim, o tamanho do código não é um problema, desde que seja "limpo" e "legível". A meu favor, devo argumentar que não estou usando nenhuma classe externa. Isso deve tornar as coisas mais acessíveis. E se houver algum comportamento comum, vamos encapsulá-lo em um CommonBehaviourClass e delegar para onde ele precisar. No livro do GOF (não é meu livro favorito para aprender, mas tem boas receitas), você encontrou este exemplo: en.wikipedia.org/wiki/… . É mais ou menos a mesma coisa que faço aqui.
Victor
14

Sim, mas é um pouco de trabalho, por isso depende de como é importante para você.

Você pode definir a classe como uma interface, escrever uma implementação de delegado e, em seguida, usar java.lang.reflect.Proxypara implementar a interface com métodos que executam a parte compartilhada e, em seguida, chamar condicionalmente o delegado.

interface Foo {
    public void bar();
    public void baz();
    public void bat();
}

class FooImpl implements Foo {
    public void bar() {
      //... <-- your logic represented by this notation above
    }

    public void baz() {
      //... <-- your logic represented by this notation above
    }

    // and so forth
}

Foo underlying = new FooImpl();
InvocationHandler handler = new MyInvocationHandler(underlying);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
     new Class[] { Foo.class },
     handler);

Você MyInvocationHandlerpode se parecer com isso (tratamento de erros e andaimes de classe omitidos, supondo que eles fooIsEnabledestejam definidos em algum lugar acessível):

public Object invoke(Object proxy, Method method, Object[] args) {
    if (!fooIsEnabled) return null;
    return method.invoke(underlying, args);
}

Não é incrivelmente bonito. Mas, diferentemente de vários comentadores, eu o faria, pois acho que a repetição é um risco mais importante que esse tipo de densidade, e você poderá produzir a "sensação" de sua classe real, com esse invólucro um tanto inescrutável adicionado em localmente em apenas algumas linhas de código.

Veja o documentação Java para obter detalhes sobre classes de proxy dinâmico.

David P. Caldwell
fonte
14

Esta questão está intimamente relacionada à programação orientada a aspectos . O AspectJ é uma extensão AOP do Java e você pode dar uma olhada para obter alguma inspiração.

Tanto quanto eu sei, não há suporte direto para AOP em Java. Existem alguns padrões do GOF relacionados a ele, como por exemplo, Método e estratégia de modelo mas ele realmente não salva as linhas de código.

Em Java e na maioria das outras linguagens, você pode definir a lógica recorrente necessária nas funções e adotar uma abordagem de codificação disciplinada, na qual você as chama no momento certo.

public void checkBalance() {
    checkSomePrecondition();
    ...
    checkSomePostcondition();
}

No entanto, isso não se adequaria ao seu caso, porque você gostaria que o código fatorado pudesse retornar checkBalance. Em linguagens que suportam macros (como C / C ++), você pode definir checkSomePreconditione checkSomePostconditioncomo macros e elas simplesmente serão substituídas pelo pré-processador antes que o compilador seja chamado:

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

Java não tem isso fora da caixa. Isso pode ofender alguém, mas eu usei a geração automática de código e os mecanismos de modelo para automatizar tarefas de codificação repetitiva no passado. Se você processar seus arquivos Java antes de compilá-los com um pré-processador adequado, por exemplo Jinja2, poderá fazer algo semelhante ao que é possível em C.

Possível abordagem Java pura

Se você está procurando uma solução Java pura, o que você pode achar provavelmente não será conciso. Porém, isso ainda pode levar em consideração partes comuns do seu programa e evitar duplicação de código e bugs. Você poderia fazer algo assim (é algum tipo de padrão inspirado na estratégia ). Observe que, em C # e Java 8, e em outros idiomas nos quais as funções são um pouco mais fáceis de manipular, essa abordagem pode realmente parecer boa.

public interface Code {
    void execute();
}

...

public class Foo {
  private bool fooIsEnabled;

  private void protect(Code c) {
      if (!fooIsEnabled) return;
      c.execute();
  }

  public void bar() {
    protect(new Code {
      public void execute() {
        System.out.println("bar");
      }
    });
  }

  public void baz() {
    protect(new Code {
      public void execute() {
        System.out.println("baz");
      }
    });
  }

  public void bat() {
    protect(new Code {
      public void execute() {
        System.out.println("bat");
      }
    });
  }
}

Meio que um cenário do mundo real

Você está desenvolvendo uma classe para enviar quadros de dados para um robô industrial. O robô leva tempo para concluir um comando. Quando o comando é concluído, ele envia um quadro de controle de volta. O robô pode ser danificado se receber um novo comando enquanto o anterior ainda estiver sendo executado. Seu programa usa uma DataLinkclasse para enviar e receber quadros de e para o robô. Você precisa proteger o acesso aoDataLink instância.

As chamadas de rosca interface de utilizador RobotController.left, right, upou downquando o usuário clica nos botões, mas também chama BaseController.ticka intervalos regulares, a fim de reencaminhamento de comando reativar para o privado DataLinkinstância.

interface Code {
    void ready(DataLink dataLink);
}

class BaseController {
    private DataLink mDataLink;
    private boolean mReady = false;
    private Queue<Code> mEnqueued = new LinkedList<Code>();

    public BaseController(DataLink dl) {
        mDataLink = dl;
    }

    protected void protect(Code c) {
        if (mReady) {
            mReady = false;
            c.ready(mDataLink);
        }
        else {
            mEnqueue.add(c);
        }
    }

    public void tick() {
        byte[] frame = mDataLink.readWithTimeout(/* Not more than 50 ms */);

        if (frame != null && /* Check that it's an ACK frame */) {
          if (mEnqueued.isEmpty()) {
              mReady = true;
          }
          else {
              Code c = mEnqueued.remove();
              c.ready(mDataLink);
          }
        }
    }
}

class RobotController extends BaseController {
    public void left(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'left' by amount */);
        }});
    }

    public void right(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'right' by amount */);
        }});
    }

    public void up(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'up' by amount */);
        }});
    }

    public void down(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'down' by amount */);
        }});
    }
}
damix911
fonte
4
Isso não apenas chuta a lata pela estrada. Ou seja, o futuro mantenedor costumava se lembrar de se (! FooIsEnabled) return; no início de cada função e agora eles precisam se lembrar de proteger (novo Código {... no início de cada função Como isso ajuda.?
bhspencer
Eu gosto da análise e do contexto em segundo plano damix911 ... eu criaria as novas instâncias de código em tempo de compilação (usando membros estáticos privados) assumindo que o código não mudará ao longo do tempo e renomearia a alteração para "executeIf" passando como argumento uma condição (Como a classe Predicado) e o Código. Mas isso é mais pessoal e gosto.
Victor
1
@bhspencer Parece bastante desajeitado em Java e, na maioria dos casos, essa estratégia é realmente uma superengenharia de código simples. Poucos programas podem se beneficiar desse padrão. O legal é que criamos um novo símbolo protectque é mais fácil de reutilizar e documentar. Se você disser ao futuro mantenedor que o código crítico deve ser protegido protect, você já está dizendo a ele o que fazer. Se as regras de proteção mudarem, o novo código ainda está protegido. Essa é exatamente a lógica por trás da definição de funções, mas o OP precisava "retornar um return" que funções não podem fazer.
precisa saber é o seguinte
11

Eu consideraria a refatoração. Esse padrão está quebrando fortemente o padrão DRY (não se repita). Eu acredito que isso quebra essa responsabilidade de classe. Mas isso depende do seu controle do código. Sua pergunta é muito aberta - para onde você está chamando Fooinstância?

Suponho que você tenha código como

foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also

talvez você deva chamar algo assim:

if (fooEnabled) {
   foo.bat();
   foo.baz();
   ...
}

E mantenha-o limpo. Por exemplo, log:

this.logger.debug(createResourceExpensiveDump())

a logger não está se perguntando se a depuração está ativada. Apenas registra.

Em vez disso, a classe de chamada precisa verificar isso:

if (this.logger.isDebugEnabled()) {
   this.logger.debug(createResourceExpensiveDump())
}

Se esta é uma biblioteca e você não pode controlar a chamada dessa classe, lance um IllegalStateExceptionque explica o motivo, se essa chamada for ilegal e causar problemas.

Gondy
fonte
6
É definitivamente mais simples e fácil para os olhos. Mas se o objetivo do OP é garantir que, à medida que novos métodos sejam adicionados, que a lógica ativada nunca seja ignorada, essa refatoração não facilitará a imposição disso.
sstan
4
Também para o seu exemplo de log, eu diria que isso envolve muito mais repetições - toda vez que você deseja fazer logon, é necessário verificar se o logger está ativado. I tendem a registrar mais linhas do que o número de métodos em qualquer classe ...
T. Kiley
4
Isso quebra a modularidade, porque agora o chamador precisa saber algo sobre os elementos internos do foo (nesse caso, se é fooEnabled). Este é um exemplo clássico em que seguir as regras das melhores práticas não resolverá o problema porque as regras entram em conflito. (Eu ainda estou esperando que alguém vai vir para cima com uma resposta que "por que não pensei nisso antes?".)
Ian Goldby
2
Bem, depende muito do contexto para saber se faz sentido.
Ian Goldby
3
O log é exatamente o exemplo, onde não desejo repetição no meu código. Eu só quero escrever LOG.debug ("...."); - E o logger deve verificar se eu realmente quero depurar. - Outro exemplo é de fechamento / limpeza. - Se eu usar o AutoClosable, não quero uma exceção, se já estiver fechada, não deve fazer nada.
Falco
6

IMHO, a solução mais elegante e com melhor desempenho para isso é ter mais de uma implementação do Foo, junto com um método de fábrica para criar uma:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : new NopFoo();
  }
}

class NopFoo extends Foo {
  public void bar() {
    // Do nothing
  }
}

Ou uma variação:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : NOP_FOO;
  }

  private static Foo NOP_FOO = new Foo() {
    public void bar() {
      // Do nothing
    }
  };
}

Como o sstan aponta, o melhor seria usar uma interface:

public interface Foo {
  void bar();

  static Foo getFoo() {
    return fooEnabled ? new FooImpl() : new NopFoo();
  }
}

class FooImpl implements Foo {
  FooImpl() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }
}

class NopFoo implements Foo {
  NopFoo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do nothing
  }
}

Adapte isso ao restante de suas circunstâncias (você está criando um novo Foo toda vez ou reutilizando a mesma instância etc.)

Pepijn Schmitz
fonte
1
Isso não é o mesmo que a resposta de Konrad. Eu gosto disso, mas acho que seria mais seguro se, em vez de usar a herança de classe, você usasse uma interface como outros sugeriram em suas respostas. O motivo é simples: é muito fácil para um desenvolvedor adicionar um método Fooe esquecer de adicionar a versão não operacional do método na classe estendida, ignorando o comportamento desejado.
sstan
2
@ Sstan Você está certo, isso seria melhor. Fiz assim para modificar o mínimo possível o exemplo original de Kristina, a fim de evitar distrações, mas isso é relevante. Vou adicionar sua sugestão à minha resposta.
Pepijn Schmitz
1
Eu acho que você perdeu um ponto. Você determina se, isFooEnabledna instanciação de Foo. É muito cedo. No código original, isso é feito quando um método é executado. O valor de isFooEnabledpode mudar nesse meio tempo.
Nicolas Barbulesco
1
@NicolasBarbulesco Você não sabe disso, fooIsEnabled pode ser uma constante. Ou poderia ser usado de uma maneira que a torne efetivamente constante. Ou poderia ser definido em alguns lugares bem definidos, para que fosse fácil obter uma nova instância do Foo sempre. Ou pode ser aceitável obter uma nova instância do Foo toda vez que for usada. Você não sabe, foi por isso que escrevi: "adapte isso ao resto de suas circunstâncias".
Pepijn Schmitz
@PepijnSchmitz - Claro, fooIsEnabled pode ser constante. Mas nada nos diz que seja constante. Então, eu considero o caso geral.
Nicolas Barbulesco
5

Eu tenho outra abordagem: tenha uma

interface Foo {
  public void bar();
  public void baz();
  public void bat();
}

class FooImpl implements Foo {
  public void bar() {
    //...
  }
  public void baz() {
    //...
  }
  public void bat() {
    //...
  }
}

class NullFoo implements Foo {
  static NullFoo DEFAULT = new NullFoo();
  public void bar() {}
  public void baz() {}
  public void bat() {}
}

}

e então você pode fazer

(isFooEnabled ? foo : NullFoo.DEFAULT).bar();

Talvez você possa até substituir o isFooEnabled com uma Foovariável que detenha o FooImplpara ser usado ou o NullFoo.DEFAULT. A chamada é mais simples novamente:

Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();

BTW, isso é chamado de "padrão nulo".

glglgl
fonte
A abordagem geral é boa, mas usar a expressão (isFooEnabled ? foo : NullFoo.DEFAULT).bar();parece um pouco desajeitado. Tenha uma terceira implementação que delegue para uma das implementações existentes. Em vez de alterar o valor de um campo, isFooEnabledo destino da delegação pode ser alterado. Isso reduz o número de ramificações no código #
SpaceTrucker
1
Mas você está deportando a culinária interna da classe Foono código de chamada! Como poderíamos saber se isFooEnabled? Este é um campo interno da classe Foo.
Nicolas Barbulesco 04/07/2015
3

Em uma abordagem funcional semelhante à resposta de @ Colin, com as funções lambda do Java 8 , é possível agrupar o recurso condicional para alternar o código de ativação / desativação em um método de proteção (executeIfEnabled ) que aceita a ação lambda, na qual o código a ser executado condicionalmente pode ser passado.

Embora no seu caso, essa abordagem não salve nenhuma linha de código, ao secar isso, agora você tem a opção de centralizar outras preocupações de alternância de recursos, além de preocupações de AOP ou depuração, como registro, diagnóstico, criação de perfil e outros.

Um benefício do uso de lambdas aqui é que os fechamentos podem ser usados ​​para evitar a necessidade de sobrecarregar o executeIfEnabledmétodo.

Por exemplo:

class Foo {
    private Boolean _fooIsEnabled;

    public Foo(Boolean isEnabled) {
        _fooIsEnabled = isEnabled;
    }

    private void executeIfEnabled(java.util.function.Consumer someAction) {
        // Conditional toggle short circuit
        if (!_fooIsEnabled) return;

        // Invoke action
        someAction.accept(null);
    }

    // Wrap the conditionally executed code in a lambda
    public void bar() {
        executeIfEnabled((x) -> {
            System.out.println("Bar invoked");
        });
    }

    // Demo with closure arguments and locals
    public void baz(int y) {
        executeIfEnabled((x) -> {
            System.out.printf("Baz invoked %d \n", y);
        });
    }

    public void bat() {
        int z = 5;
        executeIfEnabled((x) -> {
            System.out.printf("Bat invoked %d \n", z);
        });
    }

Com um teste:

public static void main(String args[]){
    Foo enabledFoo = new Foo(true);
    enabledFoo.bar();
    enabledFoo.baz(33);
    enabledFoo.bat();

    Foo disabledFoo = new Foo(false);
    disabledFoo.bar();
    disabledFoo.baz(66);
    disabledFoo.bat();
}
StuartLC
fonte
Também semelhante à abordagem do Damix, sem a necessidade de uma interface e implementações de classe anônimas com substituição de método.
StuartLC 17/07/2015
2

Como apontado em outras respostas, o Padrão de design da estratégia é um padrão de design apropriado a seguir para simplificar esse código. Eu o ilustrei aqui usando a invocação de método por meio da reflexão, mas existem vários mecanismos que você pode usar para obter o mesmo efeito.

class Foo {

  public static void main(String[] args) {
      Foo foo = new Foo();
      foo.fooIsEnabled = false;
      foo.execute("bar");
      foo.fooIsEnabled = true;
      foo.execute("baz");
  }

  boolean fooIsEnabled;

  public void execute(String method) {
    if(!fooIsEnabled) {return;}
    try {
       this.getClass().getDeclaredMethod(method, (Class<?>[])null).invoke(this, (Object[])null);
    }
    catch(Exception e) {
       // best to handle each exception type separately
       e.printStackTrace();
    }
  }

  // Changed methods to private to reinforce usage of execute method
  private void bar() {
    System.out.println("bar called");
    // bar stuff here...
  }
  private void baz() {
    System.out.println("baz called");
    // baz stuff here...
  }
  private void bat() {
    System.out.println("bat called");
    // bat stuff here...
  }
}
LJ2
fonte
Ter que lidar com a reflexão é um pouco estranho, se já existem classes que fazem isso por você, como as já mencionadas Proxy.
SpaceTrucker
Como você pôde fazer foo.fooIsEnabled ...? A priori, este é um campo interno do objeto, não podemos e não queremos vê-lo fora.
Nicolas Barbulesco 04/07/2015
2

Se ao menos o java fosse um pouco melhor em ser funcional. Ele acha que a solução mais eficiente é criar classe que agrupe uma única função, de modo que seja chamada apenas quando foo estiver ativado.

abstract class FunctionWrapper {
    Foo owner;

    public FunctionWrapper(Foo f){
        this.owner = f;
    }

    public final void call(){
        if (!owner.isEnabled()){
            return;
        }
        innerCall();
    }

    protected abstract void innerCall();
}

e depois implemente bar, baze batcomo classes anônimas que se estendem FunctionWrapper.

class Foo {
    public boolean fooIsEnabled;

    public boolean isEnabled(){
        return fooIsEnabled;
    }

    public final FunctionWrapper bar = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    public final FunctionWrapper baz = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    // you can pass in parms like so 
    public final FunctionWrapper bat = new FunctionWrapper(this){
        // some parms:
        int x,y;
        // a way to set them
        public void setParms(int x,int y){
            this.x=x;
            this.y=y;
        }

        @Override
        protected void innerCall() {
            // do whatever using x and y
        }
    };
}

Outra ideia

Use a solução anulável do glglgl, mas faça FooImple NullFooclasses internas (com construtores particulares) da classe abaixo:

class FooGateKeeper {

    public boolean enabled;

    private Foo myFooImpl;
    private Foo myNullFoo;

    public FooGateKeeper(){
        myFooImpl= new FooImpl();
        myNullFoo= new NullFoo();
    }

    public Foo getFoo(){
        if (enabled){
            return myFooImpl;
        }
        return myNullFoo;
    }  
}

Dessa forma, você não precisa se preocupar em lembrar de usar (isFooEnabled ? foo : NullFoo.DEFAULT).

Colin
fonte
Digamos que você tem: Foo foo = new Foo()para chamar barvocê escreveriafoo.bar.call()
Colin
1

Parece que a classe não faz nada quando o Foo não está ativado. Por que não expressar isso em um nível superior em que você cria ou obtém a instância do Foo?

class FooFactory
{
 static public Foo getFoo()
 {
   return isFooEnabled ? new Foo() : null;
 }
}
 ...
 Foo foo = FooFactory.getFoo();
 if(foo!=null)
 {
   foo.bar();
   ....
 }     

Isso só funciona se isFooEnabled for uma constante. Em um caso geral, você pode criar sua própria anotação.

Konrad Höffner
fonte
Konrad, você pode desenvolver a anotação?
Nicolas Barbulesco 04/07/2015
O código original determina se fooIsEnabledquando um método é chamado. Você faz isso antes da instanciação de Foo. É muito cedo. Enquanto isso, o valor pode mudar.
Nicolas Barbulesco 04/07/2015
Eu acho que você perdeu um ponto. A priori, isFooEnabledé um campo de instância de Fooobjetos.
Nicolas Barbulesco 04/07
1

Não estou familiarizado com a sintaxe Java. Suponha que em Java exista polimorfismo, propriedade estática, classe abstrata e método:

    public static void main(String[] args) {
    Foo.fooIsEnabled = true; // static property, not particular to a specific instance  

    Foo foo = new bar();
    foo.mainMethod();

    foo = new baz();
    foo.mainMethod();

    foo = new bat();
    foo.mainMethod();
}

    public abstract class Foo{
      static boolean fooIsEnabled;

      public void mainMethod()
      {
          if(!fooIsEnabled)
              return;

          baMethod();
      }     
      protected abstract void baMethod();
    }
    public class bar extends Foo {
        protected override baMethod()
        {
            // bar implementation
        }
    }
    public class bat extends Foo {
        protected override baMethod()
        {
            // bat implementation
        }
    }
    public class baz extends Foo {
        protected override baMethod()
        {
            // baz implementation
        }
    }
ehh
fonte
Quem disse que estar habilitado é propriedade estática da classe?
Nicolas Barbulesco
O que new bar()deveria significar?
Nicolas Barbulesco
Em Java, escrevemos uma classe Namecom uma letra maiúscula.
Nicolas Barbulesco
Isso precisa alterar muito o código de chamada. Chamamos um método normalmente: bar(). Se você precisar alterar isso, está condenado.
Nicolas Barbulesco 04/07/2015
1

Basicamente, você tem um sinalizador de que, se estiver definido, a chamada de função deve ser ignorada. Então, acho que minha solução seria tola, mas aqui está.

Foo foo = new Foo();

if (foo.isEnabled())
{
    foo.doSomething();
}

Aqui está a implementação de um Proxy simples, caso você queira executar algum código antes de executar qualquer função.

class Proxy<T>
{
    private T obj;
    private Method<T> proxy;

    Proxy(Method<T> proxy)
    {
        this.ojb = new T();
        this.proxy = proxy;
    }

    Proxy(T obj, Method<T> proxy)
    {
        this.obj = obj;
        this.proxy = proxy;
    }

    public T object ()
    {
        this.proxy(this.obj);
        return this.obj;
    }
}

class Test
{
    public static void func (Foo foo)
    {
        // ..
    }

    public static void main (String [] args)
    {
        Proxy<Foo> p = new Proxy(Test.func);

        // how to use
        p.object().doSomething();
    }
}

class Foo
{
    public void doSomething ()
    {
        // ..
    }
}
Khaled.K
fonte
Para seu primeiro bloco de código, você precisa de um método visível isEnabled(). A priori, ser ativado é o cozimento interno Foo, não exposto.
Nicolas Barbulesco
O código de chamada não pode e não deseja saber se o objeto está ativado .
Nicolas Barbulesco 04/07/2015
0

Existe outra solução, usando delegate (ponteiro para funcionar). Você pode ter um método exclusivo que primeiro faça a validação e depois chame o método relevante de acordo com a função (parâmetro) a ser chamada. Código c #:

internal delegate void InvokeBaxxxDelegate();

class Test
{
    private bool fooIsEnabled;

    public Test(bool fooIsEnabled)
    {
        this.fooIsEnabled = fooIsEnabled;
    }

    public void Bar()
    {
        InvokeBaxxx(InvokeBar);
    }

    public void Baz()
    {
        InvokeBaxxx(InvokeBaz);
    }

    public void Bat()
    {
        InvokeBaxxx(InvokeBat);
    }

    private void InvokeBaxxx(InvokeBaxxxDelegate invoker)
    {
        if (!fooIsEnabled) return;
        invoker();
    }

    private void InvokeBar()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bar");
    }

    private void InvokeBaz()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Baz");
    }

    private void InvokeBat()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bat");
    }
}
ehh
fonte
2
Correto, está marcado Java e é por isso que enfatizo e escrevi "Código em C #", pois não conheço Java. Como se trata de uma questão de Padrão de design, a linguagem não é importante.
ehh
Oh! Entendo, desculpe por isso, só tentei ajudar e encontrar uma solução. Obrigado
ehh