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 fooIsEnabled
parte 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.Proxy
que exige que todas las invocaciones de métodosFoo
comiencen por verificar elenabled
estado.main
método:Foo
interfaz:FooFactory
clase: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:
Foo
las implementaciones de métodos no tienen que preocuparse por la preocupaciónenabled
transversal 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.Foo
clase y, por error, "olvide" agregar elenabled
cheque. Elenabled
comportamiento de verificación se hereda automáticamente por cualquier método recién agregado.enabled
verificació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:
FooImpl
clase 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 laFoo
interfaz 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í:
main
método:BypassCheck
anotación:Foo
interfaz:FooFactory
clase: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 inFooEnabledBehavior
ybar()
inFooDisabledBehavior
podrí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.Proxy
para implementar la interfaz con métodos que hacen la parte compartida y luego llaman condicionalmente al delegado.Su
MyInvocationHandler
aspecto es posible que algo como esto (la gestión de errores y andamios clase se omite, asumiendo quefooIsEnabled
se 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 definircheckSomePrecondition
ycheckSomePostcondition
como 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
DataLink
clase para enviar y recibir marcos hacia y desde el robot. Debe proteger el acceso a laDataLink
instancia.Las llamadas de subproceso de interfaz de usuario
RobotController.left
,right
,up
odown
cuando el usuario hace clic en los botones, pero también llamaBaseController.tick
a intervalos regulares, con el fin de volver a activar el reenvío de comando a la privadaDataLink
instancia.fuente
protect
que 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
Foo
instancia?Supongo que tienes un código como
tal vez deberías llamarlo así:
Y mantenlo limpio. Por ejemplo, el registro:
a
logger
no 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
IllegalStateException
que 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
Foo
y olvidar agregar la versión no operativa del método en la clase extendida, evitando así el comportamiento deseado.isFooEnabled
en la instanciación deFoo
. Es demasiado temprano. En el código original, esto se hace cuando se ejecuta un método. El valor deisFooEnabled
puede cambiar mientras tanto.fooIsEnabled
puede 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
isFooEnabled
con unFoo
variable que contiene elFooImpl
para 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ódigoFoo
en 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
executeIfEnabled
mé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
,baz
ybat
como clases anónimas que se extiendenFunctionWrapper
.Otra idea
Uso glglgl de solución anulable pero maquillaje
FooImpl
yNullFoo
clases 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 llamarlobar
escribirí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
fooIsEnabled
se llama a un método. Haces esto antes de la instanciación deFoo
. Es demasiado temprano. El valor puede cambiar mientras tanto.isFooEnabled
es un campo de instancia deFoo
objetos.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?Name
con 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