kri*_*ina 141 java design-patterns
我有一个类,每个方法都以相同的方式启动:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
Run Code Online (Sandbox Code Playgroud)
fooIsEnabled
对于类中的每个公共方法,是否有一种很好的方法要求(并且希望不是每次都写入)该部分?
sst*_*tan 89
我不知道优雅,但这里是使用工作的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"
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如其他人所指出的那样,如果你只需要担心一些方法,那么你所需要的东西似乎有些过分.
那说,肯定有好处:
Foo
方法实现不必担心enabled
检查交叉问题.相反,该方法的代码只需要担心方法的主要目的是什么,仅此而已.Foo
班级添加新方法,并错误地"忘记"添加enabled
支票.该enabled
检查的行为由新添加的方法自动继承.enabled
检查,那么在一个地方安全地完成这项工作非常容易.Spring
,尽管它们也绝对是不错的选择.公平地说,一些缺点是:
FooImpl
是丑陋的.Foo
,则必须在2个位置进行更改:实现类和接口.没什么大不了的,但它仍然需要更多的工作.编辑:
Fabian Streitel的评论让我想到了上述解决方案的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"
}
Run Code Online (Sandbox Code Playgroud)
BypassCheck
注解:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Vic*_*tor 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() {}
}
}
Run Code Online (Sandbox Code Playgroud)
希望你喜欢!
PD:是状态模式的实现(根据上下文也称为策略......但原则是相同的).
Dav*_*ell 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);
Run Code Online (Sandbox Code Playgroud)
你MyInvocationHandler
可以看起来像这样(错误处理和类脚手架省略,假设fooIsEnabled
在某处可访问):
public Object invoke(Object proxy, Method method, Object[] args) {
if (!fooIsEnabled) return null;
return method.invoke(underlying, args);
}
Run Code Online (Sandbox Code Playgroud)
这不是非常漂亮.但是与各种评论者不同,我会这样做,因为我认为重复是比这种密度更重要的风险,并且你将能够产生真实类的"感觉",添加了这个有点不可思议的包装器非常本地只有几行代码.
有关动态代理类的详细信息,请参阅Java文档.
dam*_*911 14
这个问题与面向方面的编程密切相关.AspectJ是Java的AOP扩展,您可以试一试获得一些愿望.
据我所知,Java中没有对AOP的直接支持.有一些与它相关的GOF模式,例如模板方法和策略,但它不会真正保存你的代码行.
在Java和大多数其他语言中,您可以定义函数中所需的循环逻辑,并采用所谓的规范编码方法,您可以在适当的时间调用它们.
public void checkBalance() {
checkSomePrecondition();
...
checkSomePostcondition();
}
Run Code Online (Sandbox Code Playgroud)
但是,这不适合您的情况,因为您希望能够从中返回的因子代码checkBalance
.在支持宏的语言(如C/C++)中,您可以定义checkSomePrecondition
和checkSomePostcondition
作为宏,在调用甚至调用编译器之前,它们将简单地被预处理器替换:
#define checkSomePrecondition \
if (!fooIsEnabled) return;
Run Code Online (Sandbox Code Playgroud)
Java没有开箱即用的功能.这可能会冒犯别人,但我确实使用自动代码生成和模板引擎来自动执行过去的重复编码任务.如果您在使用合适的预处理器(例如Jinja2)编译它们之前处理Java文件,您可以执行与C中可能的类似的操作.
如果您正在寻找纯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");
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
您正在开发一个类来将数据帧发送到工业机器人.机器人需要时间来完成命令.命令完成后,它会向您发送一个控制帧.如果机器人在前一个仍在执行时收到新命令,则可能会损坏.您的程序使用DataLink
类来向机器人发送帧和从机器人接收帧.您需要保护对DataLink
实例的访问.
用户界面线程调用RobotController.left
,right
,up
或者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 */);
}});
}
}
Run Code Online (Sandbox Code Playgroud)
Gon*_*ndy 11
我会考虑重构.这种模式严重破坏了DRY模式(不要重复自己).我相信这打破了这个阶级的责任.但这取决于您对代码的控制.你的问题非常开放 - 你在哪里调用Foo
实例?
我想你的代码就像
foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also
Run Code Online (Sandbox Code Playgroud)
也许你应该这样称呼它:
if (fooEnabled) {
foo.bat();
foo.baz();
...
}
Run Code Online (Sandbox Code Playgroud)
并保持清洁.例如,记录:
this.logger.debug(createResourceExpensiveDump())
Run Code Online (Sandbox Code Playgroud)
a 如果启用了调试,则logger
不会问自己.它只是记录.
相反,调用类需要检查:
if (this.logger.isDebugEnabled()) {
this.logger.debug(createResourceExpensiveDump())
}
Run Code Online (Sandbox Code Playgroud)
如果这是一个库并且您无法控制此类的调用,请抛出一个IllegalStateException
解释原因,如果此调用是非法的并导致麻烦.
恕我直言,最优雅,性能最佳的解决方案是拥有多个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
}
}
Run Code Online (Sandbox Code Playgroud)
或者变化:
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
}
};
}
Run Code Online (Sandbox Code Playgroud)
正如斯坦斯指出的那样,更好的方法是使用界面:
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
}
}
Run Code Online (Sandbox Code Playgroud)
根据您的其他情况进行调整(您是每次创建一个新的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() {}
}
Run Code Online (Sandbox Code Playgroud)
}
然后你就可以做到
(isFooEnabled ? foo : NullFoo.DEFAULT).bar();
Run Code Online (Sandbox Code Playgroud)
也许你甚至可以isFooEnabled
用一个Foo
变量来替换它,这个变量既FooImpl
可以使用也可以使用NullFoo.DEFAULT
.然后再次呼叫更简单:
Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();
Run Code Online (Sandbox Code Playgroud)
顺便说一句,这被称为"空模式".
归档时间: |
|
查看次数: |
26305 次 |
最近记录: |