问题
我有一个类,每个方法都以相同的方式启动:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
对于班级中的每个公共方法,是否有一种很好的方法要求(并且希望不是每次都写)fooIsEnabled
part?
#1 热门回答(87 赞)
我不知道优雅,但这是一个使用Java的内置java.lang.reflect.Proxy
thatenforce的工作实现,通过检查enabled
state所有方法调用Foo
begin。
main
method:
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
interface:
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
class:
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
接口中的方法检查"。这样,我根本不需要魔术字符串,开发人员在这种特殊情况下正确添加新方法变得更加容易。
使用注释解决方案,代码如下所示:
main
method:
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
annotation:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
interface:
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
class:
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。