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 fooIsEnabled
parte para todos os métodos públicos da classe?
java
design-patterns
Cristina
fonte
fonte
Respostas:
Eu não sei sobre o elegante, mas aqui está uma implementação de trabalho usando o Java embutido
java.lang.reflect.Proxy
que impõe que todas as invocações de métodos emFoo
começam verificando oenabled
estado.main
método:Foo
interface:FooFactory
classe: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:
Foo
as implementações do método não precisam se preocupar com a preocupaçãoenabled
transversal 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.Foo
classe e, por engano, "esquecer" de adicionar aenabled
verificação. Oenabled
comportamento da verificação é herdado automaticamente por qualquer método adicionado recentemente.enabled
verificação, é muito fácil fazê-lo com segurança e em um só lugar.Spring
, embora elas também possam ser boas opções.Para ser justo, algumas das desvantagens são:
FooImpl
classe é feio.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.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:
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 naFoo
interface 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:BypassCheck
anotação:Foo
interface:FooFactory
classe:fonte
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.
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).
fonte
bar()
inFooEnabledBehavior
ebar()
inFooDisabledBehavior
podem 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.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.Proxy
para implementar a interface com métodos que executam a parte compartilhada e, em seguida, chamar condicionalmente o delegado.Você
MyInvocationHandler
pode se parecer com isso (tratamento de erros e andaimes de classe omitidos, supondo que elesfooIsEnabled
estejam definidos em algum lugar acessível):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.
fonte
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.
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 definircheckSomePrecondition
echeckSomePostcondition
como macros e elas simplesmente serão substituídas pelo pré-processador antes que o compilador seja chamado: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.
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
DataLink
classe 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
,up
oudown
quando o usuário clica nos botões, mas também chamaBaseController.tick
a intervalos regulares, a fim de reencaminhamento de comando reativar para o privadoDataLink
instância.fonte
protect
que é mais fácil de reutilizar e documentar. Se você disser ao futuro mantenedor que o código crítico deve ser protegidoprotect
, 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 umreturn
" que funções não podem fazer.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
Foo
instância?Suponho que você tenha código como
talvez você deva chamar algo assim:
E mantenha-o limpo. Por exemplo, log:
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:
Se esta é uma biblioteca e você não pode controlar a chamada dessa classe, lance um
IllegalStateException
que explica o motivo, se essa chamada for ilegal e causar problemas.fonte
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:
Ou uma variação:
Como o sstan aponta, o melhor seria usar uma interface:
Adapte isso ao restante de suas circunstâncias (você está criando um novo Foo toda vez ou reutilizando a mesma instância etc.)
fonte
Foo
e esquecer de adicionar a versão não operacional do método na classe estendida, ignorando o comportamento desejado.isFooEnabled
na instanciação deFoo
. É muito cedo. No código original, isso é feito quando um método é executado. O valor deisFooEnabled
pode mudar nesse meio tempo.fooIsEnabled
pode ser constante. Mas nada nos diz que seja constante. Então, eu considero o caso geral.Eu tenho outra abordagem: tenha uma
}
e então você pode fazer
Talvez você possa até substituir o
isFooEnabled
com umaFoo
variável que detenha oFooImpl
para ser usado ou oNullFoo.DEFAULT
. A chamada é mais simples novamente:BTW, isso é chamado de "padrão nulo".
fonte
(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,isFooEnabled
o destino da delegação pode ser alterado. Isso reduz o número de ramificações no código #Foo
no código de chamada! Como poderíamos saber seisFooEnabled
? Este é um campo interno da classeFoo
.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
executeIfEnabled
método.Por exemplo:
Com um teste:
fonte
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.
fonte
Proxy
.foo.fooIsEnabled ...
? A priori, este é um campo interno do objeto, não podemos e não queremos vê-lo fora.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.
e depois implemente
bar
,baz
ebat
como classes anônimas que se estendemFunctionWrapper
.Outra ideia
Use a solução anulável do glglgl, mas faça
FooImpl
eNullFoo
classes internas (com construtores particulares) da classe abaixo:Dessa forma, você não precisa se preocupar em lembrar de usar
(isFooEnabled ? foo : NullFoo.DEFAULT)
.fonte
Foo foo = new Foo()
para chamarbar
você escreveriafoo.bar.call()
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?
Isso só funciona se isFooEnabled for uma constante. Em um caso geral, você pode criar sua própria anotação.
fonte
fooIsEnabled
quando um método é chamado. Você faz isso antes da instanciação deFoo
. É muito cedo. Enquanto isso, o valor pode mudar.isFooEnabled
é um campo de instância deFoo
objetos.Não estou familiarizado com a sintaxe Java. Suponha que em Java exista polimorfismo, propriedade estática, classe abstrata e método:
fonte
new bar()
deveria significar?Name
com uma letra maiúscula.bar()
. Se você precisar alterar isso, está condenado.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á.
Aqui está a implementação de um Proxy simples, caso você queira executar algum código antes de executar qualquer função.
fonte
isEnabled()
. A priori, ser ativado é o cozimento internoFoo
, não exposto.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 #:
fonte