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


143

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

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

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


45
研究面向方面的编程(特别是在咨询之前)。
Sotirios Delimanolis 2015年

15
您必须使用几种样板方法?在开始介绍AOP之前,您可能需要考虑只包含一点重复代码。有时候复制和粘贴是最简单的解决方案。
bhspencer

51
我怀疑您将来的维护者会比不必学习AOP框架更愿意使用额外的模板。
bhspencer

7
如果该类的每个方法在其第一行代码中都必须执行相同的操作,则我们的设计很糟糕。
图兰斯·科尔多瓦

7
@ user1598390:这里的问题不是题外话,程序员的范围也没有什么使这个问题特别有意义的。
罗伯特·哈维

Answers:


90

我不了解优雅,但这是一个使用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,则必须在2个地方进行更改:实现类和接口。没什么大不了的,但是还有很多工作要做。
  • 代理调用不是免费的。有一定的性能开销。对于一般用途,它不会引起注意。有关更多信息,请参见此处

编辑:

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

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

要解决第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);
        }
    }
}

11
我知道这是一个聪明的解决方案,但您真的会使用吗?
bhspencer

1
这是一个不错的解决方法,您正在使用动态代理模式来装饰对象,这种对象在每种方法开始时都具有这种常见行为。
维克多

11
@bhspencer:一个非常合理的问题。实际上,我已经多次使用它来进行异常处理,日志记录,事务处理等。我承认,对于较小的类,这似乎有点过头了,而且很可能是这样。但是,如果我希望类的复杂性增加很多,并希望在这样做时确保所有方法的行为一致,那么我不介意这种解决方案。
斯坦(Sstan)2015年

1
不要成为这里97%邪恶的一部分,但是此代理类的性能含义是什么?
corsiKa 2015年

5
@corsiKa:好问题。毫无疑问,使用动态代理比直接方法调用要慢。但是,对于一般用途,性能开销是不可察觉的。相关的SO线程(如果您有兴趣):Java动态代理的性能成本
斯坦(Sstan)2015年

51

有很多不错的建议。.解决问题的方法是在状态模式下思考并实施它。

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

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

      }
}

希望你喜欢!

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


1
OP不想在每个方法的开头都重复相同的代码行,而您的解决方案涉及在每个方法的开头都重复相同的代码行。
图兰斯·科尔多瓦

2
@ user1598390不需要重复评估,在FooEnabledBehaviour内部,您假定此对象的客户端已将fooEnabled设置为true,因此无需进行检查。FooDisabledBehaviour类也是如此。再次检查,在其中进行编码。
维克多

2
感谢@ bayou.io,让我们等到OP回答。我认为社区在这里工作很麻烦,这里有很多好的提示!
维克多

2
与@dyesdyes达成协议,除了一个非常琐碎的类,我无法想象实现任何其他目的。考虑到bar()in FooEnabledBehaviorbar()in FooDisabledBehavior可能共享很多相同的代码,甚至在两者之间可能只有一行不同,这太有问题了。您可能非常容易,尤其是如果这些代码是由初级开发人员(例如我自己)维护的,最终会导致无法维护和不可测试的混乱。任何代码都可能发生这种情况,但这似乎很容易很快就搞定。+1,因为很好的建议。
克里斯·西里菲斯

1
嗯...我不是..但是首先,谢谢你的评论。对我而言,代码的大小不是问题,只要它“干净”且“可读”即可。我赞成,我应该辩称我没有使用任何外部类,这应该使事情更易于访问。并且,如果存在某些常见行为,让我们将其封装在CommonBehaviourClass中,然后委托给需要的地方。在GOF书中(不是我最喜欢的书,但是有不错的食谱),您找到了以下示例:en.wikipedia.org/wiki/…。我在这里做的大致相同。
维克多

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文档


14

这个问题与面向方面的编程紧密相关。AspectJ是Java的AOP扩展,您可以看一下以获得一些启发。

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

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

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

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

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

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

可能的纯Java方法

如果您正在寻找纯Java解决方案,那么可能会发现不够简洁。但是,它仍然可以排除程序的公共部分,并避免代码重复和错误。您可以执行类似的操作(这是一种受策略启发的模式)。请注意,在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.leftrightup或者down当用户点击按钮,而且还要求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
这不只是将罐子踢倒了。也就是说,将来的维护者必须记住(!fooIsEnabled)是否返回;在每个功能的开始,现在他们需要记住要保护(每个功能开始的新代码{...。这有什么帮助?
bhspencer

我喜欢您的分析和后台上下文damix911 ...我将在编译时(使用私有静态成员)构建新的Code实例,假设代码不会随时间变化,并将更改重命名为“ executeIf”作为条件传递给条件(如谓词类)和代码。但这更多的是个人采伐和品味。
维克多

1
@bhspencer在Java中看起来很笨拙,并且在大多数情况下,该策略实际上是对其他简单代码的过度设计。从这种模式中受益的程序并不多。但是,一件很酷的事情是我们创建了一个新符号protect,该符号更易于重用和记录。如果您告诉未来的维护者关键代码应使用进行保护protect,那么您已经在告诉他该怎么做。如果保护规则发生更改,新代码仍将受到保护。这正是定义函数背后的基本原理,但是OP需要“返回return”函数无法执行的操作。
damix911

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

a logger 不会问自己,是否启用了调试。它只是记录。

相反,调用类需要检查以下内容:

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

如果这是一个库,并且您无法控制此类的调用,则抛出一个IllegalStateException解释为什么,如果该调用非法并引起麻烦。


6
绝对简单易用。但是,如果OP的目标是确保在添加新方法时永不绕过启用的逻辑,那么这种重构并不会使其更容易实施。
斯坦(Sstan)2015年

4
同样对于您的日志示例,我想说这涉及更多的重复-每次您要登录时,都必须检查记录器是否已启用。我倾向于记录的行数超过任何类上方法的数目……
T. Kiley 2015年

4
这破坏了模块性,因为现在调用者必须了解有关foo内部的某些信息(在本例中,是否为fooEnabled)。这是一个经典示例,其中遵循最佳实践规则无法解决问题,因为规则冲突。(我仍然希望有人能提出一个“我为什么没有想到这个?”的答案。)
伊恩·戈德比

2
好吧,这在很大程度上取决于上下文是否有意义。
伊恩·高德比(Yan Goldby)2015年

3
日志记录就是一个例子,我不想在代码中重复。我只想写LOG.debug(“ ....”); -记录器应检查我是否真的要调试。-另一个例子是关闭/清理。-如果我使用AutoClosable,那么我不希望已关闭的异常,它什么也不做。
Falco

6

恕我直言,最优雅,最有效的解决方案是采用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
    }
  };
}

正如stan所指出的,更好的方法是使用一个接口:

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还是重用同一实例等)


1
这与康拉德的答案不同。我喜欢这样,但是我认为,如果您使用其他人在他们的答案中建议的接口而不是使用类继承,则会更安全。原因很简单:对于开发人员来说,将方法添加到中太容易了Foo,而忘了在扩展类中添加该方法的无操作版本,从而绕开了所需的行为。
斯坦(Sstan)2015年

2
@sstan你是对的,那会更好。我这样做是为了尽可能避免修改kristina的原始示例,以避免分心,但这是相关的。我会将您的建议添加到我的答案中。
Pepijn Schmitz

1
我想你错过了一点。您确定是否isFooEnabled在的实例化Foo。还为时过早。在原始代码中,这是在运行方法时完成的。同时,的值isFooEnabled可以更改。
Nicolas Barbulesco

1
@NicolasBarbulesco您不知道,fooIsEnabled可能是一个常量。或者可以以使其有效恒定的方式使用。或者可以将其设置在几个定义明确的位置,以便每次获取新的Foo实例都很容易。或者,每次使用Foo时都获得一个新实例是可以接受的。您不知道,这就是为什么我这样写道:“让它适应您的其余情况。”
Pepijn Schmitz

@PepijnSchmitz-当然,fooIsEnabled 可能是恒定的。但是没有什么告诉我们它是恒定的。所以我考虑一般情况。
Nicolas Barbulesco

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

也许你甚至可以取代isFooEnabledFoo其或者保持可变FooImpl使用或NullFoo.DEFAULT。然后,调用变得更加简单:

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

顺便说一句,这被称为“空模式”。


总的方法不错,但是使用表达式(isFooEnabled ? foo : NullFoo.DEFAULT).bar();似乎有点笨拙。有第三个实现,委派给现有实现之一。代替更改字段的值,isFooEnabled可以更改委托的目标。这减少了代码中的分支数量
SpaceTrucker 2015年

1
但是您正在Foo调用代码中逐级删除类的内部结构!我们怎么知道是否isFooEnabled呢?这是该类中的一个内部字段Foo
Nicolas Barbulesco

3

在类似于@Colin答案的功能方法中,使用Java 8的lambda函数,可以将条件功能切换启用/禁用代码包装到executeIfEnabled接受方法lambda 的保护方法()中,可以在该方法中执行有条件地执行的代码。通过了。

尽管就您而言,这种方法不会保存任何代码行,但通过将其干燥,您现在可以选择集中其他功能切换问题,以及AOP或调试问题,例如日志记录,诊断,配置文件等。

在这里使用lambda的好处之一是可以使用闭包来避免重载该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();
}

也类似于Damix的方法,不需要接口和带有方法重写的匿名类实现。
StuartLC

2

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

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

如果已经有类为您完成反射,那么不得不处理反射就有点尴尬了,就像已经提到的Proxy
SpaceTrucker 2015年

你该foo.fooIsEnabled ...怎么办?先验地,这是对象的内部字段,我们不能也不希望在外部看到它。
Nicolas Barbulesco

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的可为空的解决方案,但使用以下类的make FooImplNullFooinner类(带有私有构造函数):

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)


说您有:Foo foo = new Foo()打电话给bar您会写foo.bar.call()
Colin

1

似乎在未启用Foo时该类什么也不做,为什么不在创建或获取Foo实例的更高级别上表达它呢?

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

但是,仅当isFooEnabled为常数时,此方法才有效。通常,您可以创建自己的注释。


康拉德,您可以在注释上进行开发吗?
Nicolas Barbulesco

原始代码确定是否fooIsEnabled调用方法。您在实例化之前执行此操作Foo。还为时过早。该值可以同时更改。
Nicolas Barbulesco

我想你错过了一点。先验isFooEnabledFoo对象的实例字段。
Nicolas Barbulesco,2015年

1

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

谁说启用是类的静态属性?
Nicolas Barbulesco

是什么new bar()意思?
Nicolas Barbulesco

在Java中,我们Name用大写字母编写一个类。
Nicolas Barbulesco

这需要过多地更改调用代码。我们通常调用一个方法:bar()如果您需要更改它,那么您将注定要失败。
Nicolas Barbulesco

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

对于第一段代码,您需要一个visible方法isEnabled()启用的先验是的内部烹饪Foo,不公开。
Nicolas Barbulesco

调用代码不能不想,了解对象是否被启用
Nicolas Barbulesco

0

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

2
正确,它被标记为Java,这就是为什么我因为不了解Java而强调并编写了“ C#代码”。由于这是一个设计模式问题,因此语言并不重要。
ehh 2015年

哦! 我理解,对此感到抱歉,它只是试图提供帮助并找到解决方案。谢谢
ehh 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.