是否有一种优雅的方法可以使类中的每个方法都以某个代码块开头?

时间:2015-06-29 17:05:23

标签: java design-patterns

我有一个类,每个方法都以相同的方式启动:

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

对于类中的每个公共方法,是否有一种很好的方法要求(并且希望每次都不要写)fooIsEnabled部分?

14 个答案:

答案 0 :(得分:87)

我不知道优雅,但这是一个使用Java内置java.lang.reflect.Proxy的工作实现, 强制执行 ,所有方法调用{{ 1}}首先检查Foo状态。

enabled方法:

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" } 界面:

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

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); } } } 的方法实现不必担心Foo检查交叉问题。相反,该方法的代码只需要担心方法的主要目的是什么,仅此而已。
  • 无辜的开发人员无法向enabled类添加新方法,并错误地“忘记”添加Foo项检查。任何新添加的方法都会自动继承enabled检查行为。
  • 如果您需要添加其他横切关注点,或者需要增强enabled检查,则可以在一个地方安全地执行此操作。
  • 通过内置的Java功能,您可以获得类似AOP的行为,这很好。您不必被迫必须集成其他一些框架,例如enabled,尽管它们肯定也是不错的选择。

公平地说,一些缺点是:

  • 处理代理调用的一些实现代码很难看。有些人还会说,有内部类来防止Spring类的实例化是丑陋的。
  • 如果要向FooImpl添加新方法,则必须在2个位置进行更改:实现类和接口。没什么大不了的,但还是要多做点工作。
  • 代理调用不是免费的。存在一定的性能开销。但是对于一般用途,它不会引人注意。有关详细信息,请参阅here

修改

Fabian Streitel的评论让我想到了上述解决方案的两个烦恼,我承认,我对自己并不满意:

  1. 调用处理程序使用魔术字符串跳过“getEnabled”和“setEnabled”方法的“enabled-check”。如果重构方法名称,这很容易破坏。
  2. 如果需要添加不应继承“启用检查”行为的新方法,那么开发人员可能很容易弄错,至少,这意味着添加更多魔法字符串。
  3. 要解决第1点问题,并至少缓解第2点的问题,我会创建一个注释Foo(或类似的东西),我可以使用它来标记{{1}中的方法我不想执行“启用检查”的接口。这样,我根本不需要魔术字符串,开发人员在这种特殊情况下正确添加新方法变得更加容易。

    使用注释解决方案,代码如下所示:

    BypassCheck方法:

    Foo

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

    BypassCheck界面:

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

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

答案 1 :(得分:50)

有很多好的建议。你可以做些什么来解决你的问题是在State Pattern中思考并实现它。

看一下这段代码片段......也许它会让你有个想法。在这种情况下,您希望根据对象的内部状态修改整个方法实现。请回想一下,对象中方法的总和被称为行为。

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

      }
}

希望你喜欢它!

P.P:是状态模式的实现(根据上下文也称为策略......但原则是相同的)。

答案 2 :(得分:14)

是的,但它有点工作,所以这取决于它对你的重要性。

您可以将类定义为接口,编写委托实现,然后使用java.lang.reflect.Proxy实现具有共享部分的方法的接口,然后有条件地调用委托。

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

你的MyInvocationHandler看起来像这样(错误处理和类脚手架被省略,假设fooIsEnabled被定义在某处可访问):

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

这不是很漂亮。但是与各种评论者不同,我会这样做,因为我认为重复是比这种密度更重要的风险,并且你将能够产生真实类的“感觉”,添加了这个有点不可思议的包装器非常本地只有几行代码。

有关动态代理类的详细信息,请参阅Java documentation

答案 3 :(得分:14)

这个问题与aspect-oriented programming密切相关。 AspectJ是Java的AOP扩展,您可以试一试,以获得一些愿望。

据我所知,Java中没有对AOP的直接支持。有一些与之相关的GOF模式,例如Template MethodStrategy,但它不会真正保存您的代码行。

在Java和大多数其他语言中,您可以在函数中定义所需的循环逻辑,并采用所谓的规范编码方法,您可以在适当的时间调用它们。

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

然而,这不适合您的情况,因为您希望分解代码能够从checkBalance返回。在支持宏的语言(如C / C ++)中,您可以将checkSomePreconditioncheckSomePostcondition定义为宏,在调用甚至调用编译器之前,它们将被预处理器替换:

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

Java没有开箱即用的功能。这可能会冒犯别人,但我确实使用自动代码生成和模板引擎来自动执行过去的重复编码任务。如果在使用合适的预处理器(例如Jinja2)编译它们之前处理Java文件,则可以执行与C中可能的类似的操作。

可能的纯Java方法

如果您正在寻找纯Java解决方案,您可能会发现可能不会简洁。但是,它仍然可以分解程序的常见部分并避免代码重复和错误。你可以做这样的事情(这是某种Strategy - 灵感模式)。请注意,在C#和Java 8中,以及在函数更容易处理的其他语言中,这种方法实际上可能看起来不错。

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

有点真实场景

您正在开发一个类来将数据帧发送到工业机器人。机器人需要时间来完成命令。命令完成后,它会向您发送一个控制帧。如果机器人在前一个仍在执行时收到新命令,则可能会损坏。您的程序使用DataLink类来发送和接收来自机器人的帧。您需要保护对DataLink实例的访问权限。

当用户点击按钮时,用户界面线程会调用RobotController.leftrightupdown,但也会定期调用BaseController.tick,为了重新启用命令转发到私有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 */);
        }});
    }
}

答案 4 :(得分:11)

我会考虑重构。这种模式严重破坏了DRY模式(不要重复自己)。我相信这打破了这个阶级的责任。但这取决于您对代码的控制。你的问题非常开放 - 你在哪里调用Foo个实例?

我想你有像

这样的代码
foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also

也许你应该这样称呼它:

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

保持清洁。例如,记录:

this.logger.debug(createResourceExpensiveDump())

如果启用了调试,则logger 不会问自己。它只是记录。

相反,调用类需要检查:

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

如果这是一个库并且您无法控制此类的调用,请抛出一个IllegalStateException来解释原因,如果此调用是非法的并导致麻烦。

答案 5 :(得分:6)

恕我直言,最优雅,性能最佳的解决方案是拥有多个Foo实现,以及创建一个的工厂方法:

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

或变化:

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

正如斯坦斯指出的那样,更好的方法是使用界面:

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

根据您的其他情况进行调整(您是每次创建新的Foo还是重复使用相同的实例等)。

答案 6 :(得分:5)

我有另一种方法:有一个

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

}

然后你可以做

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

也许您甚至可以将isFooEnabled替换为Foo变量,该变量包含要使用的FooImplNullFoo.DEFAULT。然后再次呼叫更简单:

Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();
BTW,这被称为“空模式”。

答案 7 :(得分:3)

在使用Java 8's lambda functions的@ Colin答案的类似功能方法中,可以将条件特征包围切换启用/禁用代码转换为接受的保护方法(executeIfEnabled)动作lambda,可以传递有条件执行的代码。

虽然在您的情况下,这种方法不会保存任何代码行,但通过干预,您现在可以选择集中其他功能切换问题,加上AOP或调试问题,如日志记录,诊断,分析等。人

这里使用lambdas的一个好处是可以使用闭包来避免重载executeIfEnabled方法。

例如:

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

通过测试:

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

答案 8 :(得分:2)

正如其他答案所指出的,Strategy Design Pattern是一个合适的设计模式,可以简化此代码。我在这里通过反射使用方法调用来说明它,但是可以使用任何数量的机制来获得相同的效果。

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

答案 9 :(得分:2)

如果只有java在功能上更好一些。它认为最好的OOO解决方案是创建包装单个函数的类,因此只有在启用foo时才会调用它。

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

然后将barbazbat实现为扩展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
        }
    };
}

另一个想法

使用glglgl's nullable solution,但使以下类的FooImplNullFoo内部类(使用私有构造函数):

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;
    }  
}
通过这种方式,您不必担心记住使用(isFooEnabled ? foo : NullFoo.DEFAULT)

答案 10 :(得分:1)

当Foo未启用时,似乎该类没有任何作用,所以为什么不在创建或获取Foo实例的更高级别表达它?

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

仅当isFooEnabled是常量时才有效。 在一般情况下,您可以创建自己的注释。

答案 11 :(得分:1)

我不熟悉Java语法。 假设在Java中,存在多态性,静态属性,抽象类和&amp;方法:

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

答案 12 :(得分:1)

基本上你有一个标志,如果它被设置,应该跳过函数调用。所以我认为我的解决方案很愚蠢,但现在就是。

Foo foo = new Foo();

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

这里是一个简单代理的实现,以防你想在执行任何函数之前执行某些代码。

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

答案 13 :(得分:0)

还有另一个解决方案,使用delegate(指向函数的指针)。您可以拥有一个唯一的方法,首先进行验证,然后根据要调用的函数(参数)调用相关方法。 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");
    }
}