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

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

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

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

33540 次浏览

是的,但这是一个小工作,所以这取决于它对你有多重要。

您可以将类定义为接口,编写委托实现,然后使用 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 文档

这个问题与 面向侧面的程序设计密切相关。AspectJ 是 Java 的 AOP 扩展,您可以通过查看它来获得一些灵感。

据我所知,Java 中没有对 AOP 的直接支持。有一些 GOF 模式与之相关,例如 模板方法策略,但它不会真正节省您的代码行。

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

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

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

#define checkSomePrecondition \
if (!fooIsEnabled) return;

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

可能的纯 Java 方法

如果您正在寻找一个纯 Java 解决方案,那么您可能会发现它并不简洁。但是,它仍然可以剔除程序的常见部分,避免代码复制和 bug。你可以这样做(这是某种受 策略启发的模式)。请注意,在 C # 和 Java8中,以及在函数更容易处理的其他语言中,这种方法实际上可能看起来不错。

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

我不知道是否优雅,但是这里有一个使用 Java 内置 java.lang.reflect.Proxy的工作实现,执行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检查行为。
  • 如果你需要添加另一个横切关注点,或者如果你需要增强 enabled检查,在一个地方安全地这样做是非常容易的。
  • 您可以通过内置的 Java 功能获得类似 AOP 的行为,这是一件非常好的事情。您不必被迫集成一些其他框架,比如 Spring,尽管它们肯定也是不错的选择。

公平地说,其中一些不利因素是:

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

编辑:

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

  1. 调用处理程序使用神奇的字符串来跳过“ getEnable”和“ setEnable”方法上的“ enable-check”。如果重构方法名称,这很容易中断。
  2. 如果需要添加不应该继承“启用检查”行为的新方法,那么对于开发人员来说很容易出错,至少,这意味着添加更多神奇的字符串。

为了解决第1点,并且至少解决第2点的问题,我将创建一个注释 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();
}
}

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

我会考虑重构。这种模式是严重破坏干模式(不要重复自己)。我相信这打破了这个阶级的责任。但这取决于您对代码的控制。你的问题是非常开放的-你在哪里调用 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解释为什么,如果这个调用是非法的,并造成麻烦。

有很多好的建议。.解决问题的方法是在状态模式中思考并实现它。

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

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

正如在其他答案中指出的那样,策略设计模式是一种合适的设计模式,可以遵循它来简化这段代码。我在这里使用通过反射的方法调用进行了说明,但是您可以使用任意数量的机制来获得相同的效果。

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

我有另一个方法: 有一个

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

也许您甚至可以用一个 Foo变量替换 isFooEnabled,这个变量可以保存要使用的 FooImpl或者 NullFoo.DEFAULT。那么,这种呼吁就再次变得简单起来:

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

顺便说一下,这叫做“空模式”。

如果没有启用 Foo,这个类似乎什么也不做,那么为什么不在创建或获取 Foo 实例的更高级别上表达它呢?

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

但是,只有当 isFooEnable 是一个常量时,这才能正常工作。 在一般情况下,您可以创建自己的注释。

恕我直言,解决这个问题的最优雅和最佳方案是拥有不止一个 Foo 实现,以及创建一个 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 或重用相同的实例,等等)

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

我不熟悉 Java 语法。 假设在 Java 中有多态性、静态属性、抽象类和方法:

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

基本上,您有一个标志,如果设置了这个标志,就应该跳过函数调用。所以我觉得我的解决方案很愚蠢,但这就是了。

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

如果 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 的可空解,但使用下面类的 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)了。

在类似于@Colin 的回答的函数式方法中,使用 Java8的 lambda 函数,可以将条件特性开关启用/禁用代码封装到一个守护方法(executeIfEnabled)中,该方法接受动作 lambda,将要条件执行的代码可以传递给该方法。

虽然在您的情况下,这种方法不会节省任何代码行,通过 DRYing,您现在可以选择集中其他特性切换关注点,加上 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();
}