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

问题

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

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

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


#1 热门回答(87 赞)

我不知道优雅,但这是一个使用Java的内置java.lang.reflect.Proxythatenforce的工作实现,通过检查enabledstate所有方法调用Foobegin。

mainmethod:

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

Foointerface:

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

FooFactoryclass:

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的方法实现不必担心启用的检查横切关注点。相反,该方法的代码只需要担心方法的主要目的是什么,仅此而已。
  • 无辜的开发人员无法向Foo类添加新方法,并错误地"忘记"添加启用的检查。任何新添加的方法都会自动继承启用的检查行为。
  • 如果你需要添加另一个横切关注点,或者如果你需要增强启用检查,则可以在一个地方安全地执行此操作。
  • 使用内置的Java功能可以获得类似AOP的行为,这很好。你没有被迫必须集成像Spring这样的其他框架,尽管它们也绝对是不错的选择。

公平地说,一些缺点是:

  • 处理代理调用的一些实现代码很难看。有些人还会说,使用内部类来防止实例化FooImpl类是很丑陋的。
  • 如果要向Foo添加新方法,则必须在2个位置进行更改:实现类和接口。没什么大不了的,但它仍然需要更多的工作。
  • 代理调用不是免费的。存在一定的性能开销。但是对于一般用途,它不会引人注意。浏览此处获取更多信息。
    编辑:
    Fabian Streitel的评论让我想到了上述解决方案的两个烦恼,我承认,我对自己并不满意:
  • 调用处理程序使用魔术字符串跳过"getEnabled"和"setEnabled"方法的"启用检查"。如果方法名称被重构,这很容易破坏。
  • 如果有一种情况需要添加不应继承"启用检查"行为的新方法,那么开发人员可能很容易弄错,至少,这意味着要添加更多魔术弦。

要解决点#1,并至少缓解#2点的问题,我会创建一个annotationBypassCheck(或类似的东西),我可以用它来标记我不想执行"启用"的Foo接口中的方法检查"。这样,我根本不需要魔术字符串,开发人员在这种特殊情况下正确添加新方法变得更加容易。

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

mainmethod:

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

BypassCheckannotation:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}

Foointerface:

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

FooFactoryclass:

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

#2 热门回答(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.D:是状态模式的实现(根据上下文也称为策略......但原则是相同的)。


#3 热门回答(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);

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

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

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

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