有没有一种优雅的方法,使一个类的每个方法开始与一定的代码块?
我有一个类,每个方法都以相同的方式开始:
class Foo { public void bar() { if (!fooIsEnabled) return; //... } public void baz() { if (!fooIsEnabled) return; //... } public void bat() { if (!fooIsEnabled) return; //... } }
有没有一种很好的方法来要求(并希望不要每次都写) fooIsEnabled
部分中的每个公共方法?
我不知道优雅,但这里是一个工作实现使用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); } } }
正如其他人所指出的那样,如果您只有less数几种方法可以担心,那么您所需要的东西看起来就像是过火了。
这就是说,肯定有好处:
- 由于
Foo
的方法实现不必担心已enabled
检查横切关注,因此可以实现某些关注的分离。 相反,该方法的代码只需要担心该方法的主要目的是什么,仅此而已。 - 一个无辜的开发者没有办法为
Foo
类添加一个新的方法,并错误地“忘记”添加enabled
检查。enabled
检查行为是由任何新添加的方法自动inheritance的。 - 如果您需要添加另一个横切关注点,或者您需要增强
enabled
检查,那么在一个地方安全地执行操作就非常简单。 - 使用内置的Javafunction可以得到类似AOP的行为是很好的。 你不必被迫像
Spring
那样整合一些其他的框架,尽pipe它们肯定也是很好的select。
公平地说,一些缺点是:
- 处理代理调用的一些实现代码是丑陋的。 有些人会说,有内部类来防止
FooImpl
类的实例化是丑陋的。 - 如果你想为
Foo
添加一个新的方法,你必须在2个地方进行修改:实现类和接口。 没有什么大不了的,但还是有点多。 - 代理调用不是免费的。 有一定的性能开销。 虽然一般使用,但不会引起注意。 在这里看到更多的信息。
编辑:
Fabian Streitel的评论让我想起了我上面的解决scheme带来的两个烦恼,我承认,我对自己并不满意:
- 调用处理程序使用魔术string跳过“getEnabled”和“setEnabled”方法上的“enabled-check”。 如果重构方法名称,这很容易中断。
- 如果有一种情况需要添加不应该inheritance“启用 – 检查”行为的新方法,那么开发人员可能很容易弄错,至less意味着增加更多的魔法string。
为了解决第一点,并至less缓解点#2的问题,我会创build一个注释BypassCheck
(或类似的东西),我可以用它来标记我不想执行的Foo
接口中的方法“启用检查”。 这样一来,我根本不需要魔术string,在这种特殊情况下开发人员正确地添加新的方法变得更加容易。
使用注解解决scheme,代码如下所示:
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); } } }
有很多好的build议..你能做些什么来解决你的问题是在国家模式中思考和实施。
看看这个代码片段..也许它会让你一个想法。 在这种情况下看起来像是要根据对象的内部状态来修改整个方法的实现。 请记住,一个对象中的方法的总和是知道的行为。
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:是国家模式的一个实现(根据上下文也被称为策略,但是原理是一样的)。
这个问题与面向方面的编程密切相关。 AspectJ是Java的AOP扩展,你可以试试看看有没有什么愿望。
据我所知,Java中没有对AOP的直接支持。 有一些与之相关的GOF模式,比如模板方法和策略,但是它不会真的保存你的代码行。
在Java和大多数其他语言中,您可以定义函数中需要的循环逻辑,并采用所谓的规则编码方法,在适当的时候调用它们。
public void checkBalance() { checkSomePrecondition(); ... checkSomePostcondition(); }
然而,这不适合你的情况,因为你希望分解代码能够从checkBalance
返回。 在支持macros的语言(如C / C ++)中,可以将checkSomePrecondition
和checkSomePostcondition
定义为macros,在调用编译器之前,它们将被预处理器replace:
#define checkSomePrecondition \ if (!fooIsEnabled) return;
Java没有开箱即用。 这可能会冒犯某人,但我确实使用自动代码生成和模板引擎来自动化重复编码任务。 如果在使用合适的预处理器(例如Jinja2)编译Java文件之前处理Java文件,则可以执行与C中可能的操作类似的操作。
可能的纯Java方法
如果你正在寻找一个纯粹的Java解决scheme,你可能会发现可能不会简洁。 但是,它仍然可以将您的程序的常见部分分解出来,避免代码重复和错误。 你可以做这样的事情(这是某种策略模式)。 请注意,在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"); } }); } }
有点现实世界的场景
您正在开发一个将dataframe发送给工业机器人的课程。 机器人需要时间来完成一个命令。 一旦命令完成,它就会发回一个控制帧。 如果在前一个执行过程中收到一个新命令,机器人可能会受到损坏。 您的程序使用DataLink
类来发送和接收来自机器人的帧。 您需要保护对DataLink
实例的访问。
用户界面线程在用户单击button时right
或down
调用BaseController.tick
,而且还定期调用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.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
可以看起来像这样(error handling和类脚手架省略,假设fooIsEnabled
被定义在可访问的地方):
public Object invoke(Object proxy, Method method, Object[] args) { if (!fooIsEnabled) return null; return method.invoke(underlying, args); }
这不是非常漂亮。 但不像各种评论者,我会这样做,因为我认为重复是比这种密度更重要的风险,你将能够产生真正的类的“感觉”,这个有点不可思议的包装添加只需几行代码即可。
有关dynamic代理类的详细信息,请参阅Java文档 。
我会考虑重构。 这种模式严重打破干燥模式(不要重复自己)。 我相信这打破了这个阶级的责任。 但这取决于你对代码的控制。 你的问题是非常开放的 – 你在哪里调用Foo
实例?
我想你有类似的代码
foo.bar(); // does nothing if !fooEnabled foo.baz(); // does also nothing foo.bat(); // also
也许你应该这样称呼它:
if (fooEnabled) { foo.bat(); foo.baz(); ... }
并保持干净。 例如,日志logging:
this.logger.debug(createResourceExpensiveDump())
logger
不问自己 ,如果debugging已启用。 它只是logging。
相反,调用类需要检查这个:
if (this.logger.isDebugEnabled()) { this.logger.debug(createResourceExpensiveDump()) }
如果这是一个库,并且你不能控制这个类的调用,抛出一个IllegalStateException
,这就解释了为什么,如果这个调用是非法的并且造成麻烦。
恕我直言,最优雅的performance最好的解决scheme是有一个以上的Foo实现,以及工厂方法创build一个:
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 } }
将其调整到其他情况(您是否每次创build新的Foo或重新使用相同的实例等)
我有另一种方法:有一个
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
variables代替isFooEnabled
,该variables包含要使用的NullFoo.DEFAULT
或NullFoo.DEFAULT
。 然后再次调用更简单:
Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT; toBeUsed.bar(); toBeUsed.baz(); toBeUsed.bat();
顺便说一句,这被称为“空图案”。
在@ Colin的回答中,类似的函数方法,使用Java 8的lambda函数 ,可以将条件特性切换启用/禁用代码包装到一个guard方法( executeIfEnabled
)中,该方法接受操作lambda,代码将被有条件地执行通过。
虽然在这种情况下,这种方法不会保存任何代码行,但是现在,您可以select集中其他function切换问题,加上AOP或debugging问题,如日志logging,诊断,分析等。
在这里使用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); }); }
通过testing:
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(); }
正如在其他答案中指出的那样, 策略devise模式是一个合适的devise模式,可以简化这个代码。 我已经在这里使用方法调用通过reflection来说明它,但是可以使用任意数量的机制来获得相同的效果。
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... } }
如果只有javafunction更好一点。 它认为最有效的解决scheme是创build类包装一个单一的function,所以只有当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(); }
然后实现bar
, baz
和bat
作为扩展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的可为空的解决scheme,但使下面的类的FooImpl
和NullFoo
内部类(与私有构造函数):
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没有启用的时候,这个类似乎什么也不做,为什么不在更高的层次上expression你创build或者获得Foo实例呢?
class FooFactory { static public Foo getFoo() { return isFooEnabled ? new Foo() : null; } } ... Foo foo = FooFactory.getFoo(); if(foo!=null) { foo.bar(); .... }
这只适用于isFooEnabled是一个常量。 在一般情况下,您可以创build自己的注释。
我不熟悉Java语法。 Assumption that in Java, there is polymorphism, static property, abstract class & method:
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 } }
Basically you have a flag that if it's set, the function call should be skipped. So I think my solution would be silly, but here it is.
Foo foo = new Foo(); if (foo.isEnabled()) { foo.doSomething(); }
Here's the implementation of a simple Proxy, in case you want to execute some code before executing any function.
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 () { // .. } }
There is another solution, using delegate (pointer to function). You can have a unique method that first is doing the validation and then is calling to the relevant method according to the function (parameter) to be called. C# code:
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"); } }