对于一个学校项目,我需要用Java编写一个简单的服务器,它可以持续侦听传入的目录,并将文件从该目录移动到其他地方。服务器需要记录信息和错误消息,所以我想我可以使用代理模式。因此,我创建了以下ServerInterface:
public interface ServerInterface extends Runnable {
public void initialize(String repPath, ExecutorInterface executor, File propertiesFile) throws ServerInitException;
public void run();
public void terminate();
public void updateHTML();
public File[] scanIncomingDir();
public List<DatasetAttributes> moveIncomingFilesIfComplete(File[] incomingFiles);
}
然后,我创建了一个表示真实对象的实现Server
和一个表示代理的类ProxyServer
。此外,Server
还有一个工厂方法,该方法创建ProxyServer
对象,但将其作为ServerInterface
返回。
代理对象上的run
-方法如下所示:
@Override
public void run(){
log(LogLevels.INFO, "server is running ...");
while( !stopped ){
try {
File[] incomingContent = scanIncomingDir();
moveIncomingFilesIfComplete(incomingContent);
updateHTML();
pause();
} catch (Exception e) {
logger.logException(e, new Timestamp(timestampProvider.getTimestamp()));
pause();
}
}
log(LogLevels.INFO, "server stopped");
}
try
语句中调用的函数只需记录一些内容,然后将调用传播到实际对象。到目前为止,一切都很好。但现在我已经在代理对象中以这种方式实现了run
-方法,实际对象中的run
-方法就过时了,因此它只是空的(terminate
-方法也是如此)。
所以我问自己:可以吗?代理模式应该这样实现吗?
在我看来,我把"真实"one_answers"代理"行为混为一谈。。。通常情况下,真正的服务器应该"卡"在while循环中,而不是代理服务器,对吧?我试图避免混淆这一点,但两种方法都不令人满意:
我可以在真实对象中实现
run
-方法,然后将代理对象移交给真实对象,以便在while循环期间仍然能够登录。但是,实际对象会进行一些日志记录,这是我最初试图通过编写代理来避免的。我可以说,只有代理服务器是
Runnable
,因此从接口中删除了run
和terminate
,但这会破坏代理模式。
我应该使用另一种设计吗?或者我看到了一个根本没有的问题?
您的想法肯定是正确的。你偶然发现了一个有趣的想法。
正如您所描述的,像日志记录这样的功能是我们在面向方面编程中所称的交叉关注点的一个例子。
- 交叉关注是一个将在许多对象中使用的要求
因此,他们有打破面向对象编程的趋势。这是什么意思?
- 如果您试图创建一个完全关于将文件从位置a移动到位置B的类,并且实现该类的方法首先涉及日志记录(然后是事务,然后是安全性),那么这不是很OO,是吗?它打破了单一责任原则
进入面向方面编程
这就是我们拥有AOP的原因——它的存在是为了模块化和封装这些交叉关注点。它的工作原理如下:
- 定义要应用横切功能的所有位置
- 使用截距设计图案在该特征中"编织"
使用AOP"编织"需求的方法
- 一种方法是使用Java DynamicProxy,正如您所描述的那样。这是例如Spring Framework中的默认设置。这只适用于接口
- 另一种方法是使用字节码工程库,如asm、cglib、Javassist,这些库在运行时拦截类加载器以提供新的子类
- 第三种方法是使用编译时编织——在编译时更改代码(或字节码)
- 还有一种方法是使用java代理(JVM的一个参数)
后两个选项在AspectJ中受支持。
结论:
听起来好像您正在向面向方面编程(AOP)迈进,所以请检查一下。还要注意的是,Spring Framework有很多功能可以简化AOP的应用,尽管在您的情况下,考虑到这是一项学校作业,深入研究AOP本身背后的核心概念可能会更好。
NB:如果您正在构建一个生产级服务器,日志记录可能是一个全面的功能,因此值得使用AOP。在其他情况下,它可能足够简单,可以直接联机
在这种情况下,您应该使用Observer模式:
观察者模式是一种软件设计模式,称为主题,维护其从属项的列表,称为并自动通知他们任何状态变化,通常通过调用它们的一个方法。
您的Observable
将通过时间池或此处已经建议的WatchService
来观察目录中的更改。目录的更改将通知Observer,后者将采取移动文件的操作。可观察和观察者都应记录其操作。
您还应该知道,Observer模式通过实现java.util.Observable
和java.util.Observer
而成为Java JDK的一部分。
您可以让您的代理知道真实对象。基本上,您的代理将把对run方法的调用委托给实际实现。
在委派之前,代理首先记录启动。委托后,代理记录"关机":
// Snapshot from what should look like the run method implementation
// in your proxy.
public ServerInterfaceProxy(ServerInterface target){
this.proxiedTarget = target;
}
public void run(){
log(LogLevels.INFO, "server is running ...");
this.proxiedTarget.run();
log(LogLevels.INFO, "server is running ...");
}
这个实现也可以被看作是Decorator模式的实现。IMHO,我相信在某种程度上(当涉及到实现时)Proxy和Decorator是等效的:两者都是目标的拦截/捕获行为。
查看Java 7的WatchService类。
使用代理行为来实现这一点几乎肯定有些过头了。