patterns pattern patrones method first diseño book java design-patterns

java - pattern - ¿Hay una manera elegante de hacer que cada método en una clase comience con un determinado bloque de código?



java factory pattern (14)

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á.

Foo foo = new Foo(); if (foo.isEnabled()) { foo.doSomething(); }

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.

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

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 parte fooIsEnabled para cada método público en la clase?


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 #:

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"); } }


No estoy familiarizado con la sintaxis de Java. Asunción de que en Java hay polimorfismo, propiedad estática, clase y método abstractos

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


Parece que la clase no hace nada cuando Foo no está habilitado, entonces ¿por qué no expresar esto en un nivel superior donde creas u obtienes la instancia de Foo?

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

Sin embargo, esto solo funciona si isFooEnabled es una constante. En un caso general, puede crear su propia anotación.


Como se señala en otras respuestas, el Strategy 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.

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... } }


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 instancia de Foo ?

Supongo que tienes un código como

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

tal vez deberías llamarlo así:

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

Y mantenlo limpio. Por ejemplo, el registro:

this.logger.debug(createResourceExpensiveDump())

un logger no se pregunta si la depuración está habilitada. Solo registra.

En cambio, la clase de llamada debe verificar esto:

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

Si se trata de una biblioteca y no puede controlar las llamadas de esta clase, arroje una IllegalStateException que explica por qué, si esta llamada es ilegal y causa problemas.


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:

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

O una variación:

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 señala sstan, aún mejor sería usar una interfaz:

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 esto al resto de sus circunstancias (¿está creando un nuevo Foo cada vez o reutilizando la misma instancia, etc.)


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 el código que se ejecuta condicionalmente puede ser pasado.

Aunque 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 método executeIfEnabled .

Por ejemplo:

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

Con una prueba:

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(); }


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 de GOF que se relacionan con él, como por ejemplo, el Método de plantilla y la Strategy pero en realidad no le ahorrará líneas de código.

En Java y en la mayoría de los otros lenguajes, podría 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.

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

Sin embargo, esto no encajaría en su caso porque desea que el código factorizado pueda regresar de checkBalance . En los lenguajes que admiten macros (como C / C ++), podría definir checkSomePrecondition y checkSomePostcondition como macros y simplemente serían reemplazados por el preprocesador antes de que se invoque el compilador:

#define checkSomePrecondition / if (!fooIsEnabled) return;

Java no tiene esto fuera de la caja. Esto puede ofender a alguien, pero en el pasado utilicé motores de generación automática de plantillas y plantillas para automatizar tareas de codificación repetitivas. 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 Strategy ). 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.

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"); } }); } }

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 utiliza una clase DataLink para enviar y recibir tramas hacia y desde el robot. DataLink proteger el acceso a la instancia de DataLink .

El hilo de la interfaz de usuario llama a RobotController.left , right , up o down cuando el usuario hace clic en los botones, pero también llama a BaseController.tick a intervalos regulares, para volver a habilitar el reenvío de comandos a la instancia privada de DataLink .

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


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 la implementación completa 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.

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 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).


No sé acerca de elegante, pero aquí hay una implementación funcional que usa el Java incorporado java.lang.reflect.Proxy que obliga a que todas las invocaciones de métodos en Foo comiencen por verificar el estado enabled .

método main :

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" }

Interfaz Foo :

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(); } }

Clase FooFactory :

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 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:

  • Se logra una cierta separación de preocupaciones, porque las implementaciones de métodos de Foo no tienen que preocuparse por la preocupación transversal de verificación enabled . 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.
  • No hay forma de que un desarrollador inocente agregue un nuevo método a la clase Foo y, por error, "olvide" agregar la verificación enabled . El comportamiento de verificación enabled se hereda automáticamente por cualquier método recién agregado.
  • Si necesita agregar otra preocupación transversal, o si necesita mejorar la verificación enabled , es muy fácil hacerlo de manera segura y en un solo lugar.
  • Es agradable que pueda obtener este comportamiento similar a AOP con la funcionalidad Java incorporada. No está obligado a tener que integrar algún otro marco como Spring , aunque definitivamente también pueden ser buenas opciones.

Para ser justos, algunos de los inconvenientes son:

  • Parte del código de implementación que maneja las invocaciones de proxy es feo. Algunos también dirían que tener clases internas para evitar la FooImpl de instancias de la clase FooImpl es feo.
  • Si desea agregar un nuevo método a 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.
  • Las invocaciones de poder no son gratuitas. Hay una cierta sobrecarga de rendimiento. Sin embargo, para uso general, no se notará. Ver here para más información.

EDITAR:

El comentario de Fabian Streitel me hizo pensar en 2 molestias con mi solución anterior que, admito, no estoy contento conmigo mismo:

  1. El controlador de invocación utiliza cadenas mágicas para omitir la "comprobación habilitada" en los métodos "getEnabled" y "setEnabled". Esto puede romperse fácilmente si se refactorizan los nombres de los métodos.
  2. Si hubo un caso en el que se deben agregar nuevos métodos que no deberían heredar el comportamiento de "verificación habilitada", entonces puede ser bastante fácil para el desarrollador equivocarse y, al menos, significaría agregar más magia instrumentos de cuerda.

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 usar para marcar los métodos en la interfaz de Foo para los que no quiero realizar el "cheque habilitado". 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í:

método main :

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" }

Anotación BypassCheck :

import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface BypassCheck { }

Interfaz Foo :

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(); } }

Clase FooFactory :

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


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 usar java.lang.reflect.Proxy para implementar la interfaz con métodos que hacen la parte compartida y luego llaman condicionalmente al 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);

Su MyInvocationHandler puede verse así (se omite el manejo de errores y el andamiaje de clase, suponiendo que fooIsEnabled esté definido en algún lugar accesible):

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

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.


Si solo Java fuera un poco mejor en ser funcional. Piensa que la solución más OOO es crear una clase que envuelva una sola función, por lo que solo se llama cuando foo está habilitado.

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(); }

y luego implementa bar , baz y bat como clases anónimas que extienden 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 } }; }

Otra idea

Utilice la solución anulable de glglgl pero NullFoo las clases internas FooImpl y NullFoo (con constructores privados) de la siguiente clase:

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; } }

de esta manera no tiene que preocuparse por recordar usar (isFooEnabled ? foo : NullFoo.DEFAULT) .


Tengo otro enfoque: tener un

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() {} }

}

y luego puedes hacer

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

Tal vez incluso pueda reemplazar isFooEnabled con una variable Foo que contenga el FooImpl que se utilizará o el NullFoo.DEFAULT . Entonces la llamada es más simple nuevamente:

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

Por cierto, esto se llama el "patrón nulo".