Tengo una clase donde cada método comienza de la misma manera:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
¿Hay una buena manera de requerir (y con suerte no escribir cada vez) la fooIsEnabledparte para cada método público en la clase?
java
design-patterns
kristina
fuente
fuente

Respuestas:
No sé acerca de elegante, pero aquí hay una implementación funcional que usa el Java incorporado
java.lang.reflect.Proxyque exige que todas las invocaciones de métodosFoocomiencen por verificar elenabledestado.mainmétodo:Foointerfaz:FooFactoryclase:Como otros han señalado, parece excesivo lo que necesita si solo tiene que preocuparse por un puñado de métodos.
Dicho esto, ciertamente hay beneficios:
Foolas implementaciones de métodos no tienen que preocuparse por la preocupaciónenabledtransversal de la verificación. En cambio, el código del método solo necesita preocuparse sobre cuál es el propósito principal del método, nada más.Fooclase y, por error, "olvide" agregar elenabledcheque. Elenabledcomportamiento de verificación se hereda automáticamente por cualquier método recién agregado.enabledverificación, es muy fácil hacerlo de manera segura y en un solo lugar.Spring, aunque definitivamente también pueden ser buenas opciones.Para ser justos, algunos de los inconvenientes son:
FooImplclase es feo.Foo, debe realizar un cambio en 2 puntos: la clase de implementación y la interfaz. No es un gran problema, pero sigue siendo un poco más de trabajo.EDITAR:
El comentario de Fabian Streitel me hizo pensar en 2 molestias con mi solución anterior que, admito, no estoy contento conmigo mismo:
Para resolver el punto n. ° 1 y, al menos, aliviar el problema con el punto n. ° 2, crearía una anotación
BypassCheck(o algo similar) que podría utilizar para marcar los métodos en laFoointerfaz para los que no quiero realizar el " verificación habilitada ". De esta manera, no necesito cadenas mágicas en absoluto, y se vuelve mucho más fácil para un desarrollador agregar correctamente un nuevo método en este caso especial.Usando la solución de anotación, el código se vería así:
mainmétodo:BypassCheckanotación:Foointerfaz:FooFactoryclase:fuente
Hay muchas buenas sugerencias ... lo que puede hacer para resolver su problema es pensar en el Patrón de estado e implementarlo.
Eche un vistazo a este fragmento de código ... tal vez lo lleve a una idea. En este escenario, parece que desea modificar toda la implementación de los métodos en función del estado interno del objeto. Recuerde que la suma de los métodos en un objeto se conoce como comportamiento.
¡Espero que te guste!
PD: es una implementación del patrón de estado (también conocido como estrategia según el contexto ... pero los principios son los mismos).
fuente
bar()en cuenta que inFooEnabledBehaviorybar()inFooDisabledBehaviorpodrían compartir mucho del mismo código, con quizás incluso una sola línea diferente entre los dos. Podría muy fácilmente, especialmente si este código fuera mantenido por desarrolladores junior (como yo), terminar con un desorden gigante de basura que es imposible de mantener y de verificar. Eso puede suceder con cualquier código, pero esto parece tan fácil de fastidiar realmente rápido. +1 sin embargo, porque buena sugerencia.Sí, pero es un poco de trabajo, por lo que depende de lo importante que sea para ti.
Puede definir la clase como una interfaz, escribir una implementación de delegado y luego usarla
java.lang.reflect.Proxypara implementar la interfaz con métodos que hacen la parte compartida y luego llaman condicionalmente al delegado.Su
MyInvocationHandleraspecto es posible que algo como esto (la gestión de errores y andamios clase se omite, asumiendo quefooIsEnabledse define en algún lugar accesible):No es increíblemente bonito. Pero a diferencia de varios comentaristas, lo haría, ya que creo que la repetición es un riesgo más importante que este tipo de densidad, y podrá producir la "sensación" de su clase real, con este envoltorio algo inescrutable agregado en muy localmente en solo un par de líneas de código.
Consulte la documentación de Java para obtener detalles sobre las clases de proxy dinámico.
fuente
Esta pregunta está estrechamente relacionada con la programación orientada a aspectos . AspectJ es una extensión AOP de Java y puede echarle un vistazo para obtener algo de inspiración.
Hasta donde yo sé, no hay soporte directo para AOP en Java. Hay algunos patrones GOF que se relacionan con él, como por ejemplo, el Método de plantilla y la Estrategia, pero en realidad no le ahorrará líneas de código.
En Java y en la mayoría de los otros lenguajes, puede definir la lógica recurrente que necesita en las funciones y adoptar el llamado enfoque de codificación disciplinada en el que los llama en el momento adecuado.
Sin embargo, esto no encajaría en su caso porque le gustaría que el código factorizado pueda regresar
checkBalance. En los lenguajes que admiten macros (como C / C ++), podría definircheckSomePreconditionycheckSomePostconditioncomo macros y simplemente serían reemplazados por el preprocesador antes de que se invoque el compilador:Java no tiene esto fuera de la caja. Esto puede ofender a alguien, pero utilicé motores de generación automática de plantillas y plantillas para automatizar tareas de codificación repetitivas en el pasado. Si procesa sus archivos Java antes de compilarlos con un preprocesador adecuado, por ejemplo Jinja2, podría hacer algo similar a lo que es posible en C.
Posible enfoque de Java puro
Si está buscando una solución Java pura, lo que puede encontrar probablemente no sea conciso. Sin embargo, aún podría factorizar partes comunes de su programa y evitar la duplicación de código y los errores. Podrías hacer algo como esto (es una especie de patrón inspirado en la estrategia ). Tenga en cuenta que en C # y Java 8, y en otros lenguajes en los que las funciones son un poco más fáciles de manejar, este enfoque en realidad puede verse bien.
Un poco como un escenario del mundo real
Está desarrollando una clase para enviar marcos de datos a un robot industrial. El robot toma tiempo para completar un comando. Una vez que se completa el comando, le devuelve un marco de control. El robot puede dañarse si recibe un nuevo comando mientras el anterior aún se está ejecutando. Su programa usa una
DataLinkclase para enviar y recibir marcos hacia y desde el robot. Debe proteger el acceso a laDataLinkinstancia.Las llamadas de subproceso de interfaz de usuario
RobotController.left,right,upodowncuando el usuario hace clic en los botones, pero también llamaBaseController.ticka intervalos regulares, con el fin de volver a activar el reenvío de comando a la privadaDataLinkinstancia.fuente
protectque es más fácil de reutilizar y documentar. Si le dice al futuro mantenedor que se debe proteger el código críticoprotect, ya le está diciendo qué hacer. Si las reglas de protección cambian, el nuevo código aún está protegido. Esta es exactamente la razón detrás de la definición de funciones, pero el OP necesitaba "devolver unreturn" que las funciones no pueden hacer.Consideraría refactorizar. Este patrón está rompiendo fuertemente el patrón SECO (No se repita). Creo que esto rompe esta responsabilidad de clase. Pero esto depende de su control de código. Tu pregunta es muy abierta: ¿a dónde llamas
Fooinstancia?Supongo que tienes un código como
tal vez deberías llamarlo así:
Y mantenlo limpio. Por ejemplo, el registro:
a
loggerno se pregunta si la depuración está habilitada. Solo registra.En cambio, la clase de llamada debe verificar esto:
Si se trata de una biblioteca y no puede controlar las llamadas de esta clase, arroje una
IllegalStateExceptionque explique por qué, si esta llamada es ilegal y causa problemas.fuente
En mi humilde opinión, la solución más elegante y de mejor rendimiento para esto es tener más de una implementación de Foo, junto con un método de fábrica para crear uno:
O una variación:
Como señala sstan, aún mejor sería usar una interfaz:
Adapte esto al resto de sus circunstancias (¿está creando un nuevo Foo cada vez o reutilizando la misma instancia, etc.)
fuente
Fooy olvidar agregar la versión no operativa del método en la clase extendida, evitando así el comportamiento deseado.isFooEnableden la instanciación deFoo. Es demasiado temprano. En el código original, esto se hace cuando se ejecuta un método. El valor deisFooEnabledpuede cambiar mientras tanto.fooIsEnabledpuede ser constante. Pero nada nos dice que sea constante. Entonces considero el caso general.Tengo otro enfoque: tener un
}
y luego puedes hacer
Tal vez incluso puedas reemplazar el
isFooEnabledcon unFoovariable que contiene elFooImplpara ser utilizado o elNullFoo.DEFAULT. Entonces la llamada es más simple nuevamente:Por cierto, esto se llama el "patrón nulo".
fuente
(isFooEnabled ? foo : NullFoo.DEFAULT).bar();parece un poco torpe. Tener una tercera implementación que delegue a una de las implementaciones existentes. En lugar de cambiar el valor de un campoisFooEnabled, se podría cambiar el objetivo de la delegación. Esto reduce la cantidad de ramas en el códigoFooen el código de llamada! ¿Cómo podríamos saber siisFooEnabled? Este es un campo interno en la claseFoo.En un enfoque funcional similar a la respuesta de @ Colin, con las funciones lambda de Java 8 , es posible ajustar el código de activación / desactivación de la función condicional en un método de protección (
executeIfEnabled) que acepta la acción lambda, a la que se puede ejecutar el código condicionalmente pasadoAunque en su caso, este enfoque no guardará ninguna línea de código, al SECAR esto, ahora tiene la opción de centralizar otras inquietudes de alternancia de funciones, más AOP o inquietudes de depuración como el registro, el diagnóstico, el perfil, etc.
Una ventaja de usar lambdas aquí es que los cierres se pueden usar para evitar la necesidad de sobrecargar el
executeIfEnabledmétodo.Por ejemplo:
Con una prueba:
fuente
Como se señala en otras respuestas, el Patrón de diseño de estrategia es un patrón de diseño apropiado a seguir para simplificar este código. Lo he ilustrado aquí usando la invocación de métodos a través de la reflexión, pero hay varios mecanismos que podría usar para obtener el mismo efecto.
fuente
Proxy.foo.fooIsEnabled ...? A priori, este es un campo interno del objeto, no podemos y no queremos verlo afuera.Si tan solo Java fuera un poco mejor para ser funcional. Piensa que la solución más OOO es crear una clase que envuelva una sola función, por lo que se llama solo cuando foo está habilitado.
y luego implementar
bar,bazybatcomo clases anónimas que se extiendenFunctionWrapper.Otra idea
Uso glglgl de solución anulable pero maquillaje
FooImplyNullFooclases internas (con constructores privados) de la clase a continuación:de esta manera no tiene que preocuparse por recordar usar
(isFooEnabled ? foo : NullFoo.DEFAULT).fuente
Foo foo = new Foo()para llamarlobarescribiríafoo.bar.call()Parece que la clase no hace nada cuando Foo no está habilitado, así que ¿por qué no expresar esto en un nivel superior donde creas u obtienes la instancia de Foo?
Sin embargo, esto solo funciona si isFooEnabled es una constante. En un caso general, puede crear su propia anotación.
fuente
fooIsEnabledse llama a un método. Haces esto antes de la instanciación deFoo. Es demasiado temprano. El valor puede cambiar mientras tanto.isFooEnabledes un campo de instancia deFooobjetos.No estoy familiarizado con la sintaxis de Java. Suposición de que en Java hay polimorfismo, propiedad estática, clase y método abstractos:
fuente
new bar()supone que significa?Namecon mayúscula.bar(). Si necesitas alterar eso, estás condenado.Básicamente, tiene un indicador de que, si está configurado, se debe omitir la llamada a la función. Así que creo que mi solución sería tonta, pero aquí está.
Aquí está la implementación de un Proxy simple, en caso de que desee ejecutar algún código antes de ejecutar cualquier función.
fuente
isEnabled(). A priori, estar habilitado es una cocción internaFoo, no expuesta.Hay otra solución, usar delegado (puntero para funcionar). Puede tener un método único que primero realiza la validación y luego llama al método relevante de acuerdo con la función (parámetro) a llamar. Código C #:
fuente