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

2022-08-31 07:49:16

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

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

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


答案 1

我不知道优雅,但这里有一个使用Java内置功能的工作实现,它强制所有方法调用都从检查状态开始。java.lang.reflect.ProxyFooenabled

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

正如其他人所指出的那样,如果您只有少数几种方法需要担心,那么对于您需要的东西来说,这似乎确实有点过分了。

也就是说,肯定有好处:

  • 实现了某种关注点分离,因为 的方法实现不必担心检查横切问题。相反,方法的代码只需要担心方法的主要用途是什么,仅此而已。Fooenabled
  • 无辜的开发人员无法向类中添加新方法并错误地“忘记”添加检查。任何新添加的方法都会自动继承检查行为。Fooenabledenabled
  • 如果您需要添加另一个跨领域问题,或者如果您需要增强检查,那么在一个地方安全地执行此操作非常容易。enabled
  • 您可以通过内置的Java功能获得这种类似AOP的行为,这真是太好了。您不必被迫集成其他一些框架,例如,尽管它们也绝对是不错的选择。Spring

公平地说,一些缺点是:

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

编辑:

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

  1. 调用处理程序使用魔术字符串跳过“getEnabled”和“setEnabled”方法上的“enabled-check”。如果重构方法名称,这很容易中断。
  2. 如果存在需要添加不应继承“enabled-check”行为的新方法的情况,那么开发人员很容易犯错,至少,这意味着添加更多的魔术字符串。

为了解决第 1 点,并至少缓解第 2 点的问题,我将创建一个注释(或类似的东西),我可以使用该注释来标记我不想执行“启用检查”的界面中的方法。这样,我根本不需要魔术字符串,并且开发人员在这种特殊情况下正确添加新方法变得更加容易。BypassCheckFoo

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

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

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

答案 2

有很多好的建议。.你可以做的来解决你的问题,就是在状态模式中思考并实现它。

看看这个代码片段。也许它会让你有一个想法。在此方案中,您可能希望根据对象的内部状态修改整个方法实现。请记住,对象中方法的总和称为行为。

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:是国家模式的实现(根据上下文也称为策略......但原则是相同的)。


推荐