上一篇博客一步步学习java并发编程模式之Active Object模式(三)中我们已经按照Active Object模式的各个角色,用java的包进行了分层,也明白了各个角色的职责和调用关系。模拟的代码存在什么问题呢?对于大侠来说,可能看一眼代码层次、组织和调用关系就能指出很多问题。评价一个模块的好坏,往往可以通过新增一个类似功能,看看我们需要改动的代码量和改动的难易程度来判断。对于好的软件模块,是符合开闭原则的。现在我们以新增1个类似服务的方式,来慢慢发现存在的问题。
假如我们需要提供一个耗时的计算2个经纬度之间路网距离的服务,真正的服务提供类在Active Object中是Servant角色。我们可以模仿WeatherServant来编写DistanceServant。存在这样1个问题,如果计算天气或路网距离的实现细节改变了,那我们必须要修改我们模拟active object框架的源码,这就是模拟代码存在的第一个问题。现在是高层抽象(active
object框架)依赖于低层细节(DistanceServant和WeatherServant),这是典型的设计坏味道。
现在实际的服务类我们已经有了,怎么将网距离服务用Active Object模式进行封装呢?这个其实比较简单,直接按照天气计算服务的封装服务,改下各个角色的名称和实现代码即可。这种方式有点简单粗暴,很不负责任,坏处很多,很重要的一点就是产生重复代码。但是如果我们发现所有代码都不能复用的时候,cpoy和paste是我们唯一的选择了。如果出现这种我们需要大面积cpoy和paste的时候,意味着我们的代码存在缺陷,需要进行重构解决。
现在我们按照TDD(测试驱动开发)的思想,先写我们的测试用例。我们可以参考天气的测试类ActiveObjectTest,很容易写出如下的测试类:
public static void testAsyncCall() throws Exception { // 1.调用路网距离计算服务,开始计算2个位置之间的距离 DistanceFuture future = DistanceProxy.getDistance(100,50); // 2.当前线程没有阻塞,仍然可以继续执行. System.out.println("I am still running."); // 3.The current thread is not blocked, do something else here... // Thread.sleep(5 * 1000); // 4.与天气计算结果无关的代码执行完毕. System.out.println("Now,i really need distance result to continue."); // 5.如果路网距离计算还没有结束,那么当前线程挂起,等候计算完成. int distance = future.get(); System.out.println("路网距离是:" + distance); }很容易看出,天气和路网距离的测试代码是很相似的。这并不是重复代码,这意味着类似的服务,调用者的调用方式相同,这样的一致性是很好的,可以减少调用者的学习成本。现在eclipse会报错,因为目前我们还没有编写DistanceFuture和DistanceProxy的实现。回顾天气计算服务的代码,我们发现到目前为止DistanceFuture和DistanceProxy不能复用的。于是我们不得不写一个DistanceFuture和DistanceProxy,按照eclipse的报错提示,我们一步步copy and paste天气服务的相关代码,最终为了完成距离服务,我们需要要部拷贝和修改天气服务中的所有类,因为这些类都是实现类不能复用。至此我们发现了第二个问题:代码不能复用,不能复用就意味着重复代码,这也是典型的代码坏味道。
主要是因为我们第三篇博客,违法了面向对象的solid原则中的DIP和OCP原则。现在我们使用java泛型和面向接口编程的原则,重构下之前的代码。
由于不同的任务返回结果不同,但是却有着同样的操作接口Future,所以Future必须支持泛型,来消除返回值类型差异对代码的影响。
package frame.future; public class Future<T> { private boolean isDone = false; private T result = null; public T get() { while (!isDone) { } return result; } public void setDone(boolean isDone) { this.isDone = isDone; } public void setResult(T result) { this.result = result; } }下面我们编写servant接口和对应的天气实现类
package frame.servant; public interface Servant<T> { public T call() throws Exception; }
package frame.servant; import activeobject.aty.result.WeatherResult; public class WeatherServant implements Servant<WeatherResult> { @Override public WeatherResult call() throws Exception { Thread.sleep(2 * 1000); WeatherResult result = new WeatherResult(); result.setTemperature(28); result.setWindDirection("西北风"); result.setDampness("74%"); return result; } }
接着我们编写通用的method request
package frame.methodrequest; import frame.future.Future; import frame.servant.Servant; public abstract class MethodRequest<T> { protected Servant<T> servant; protected Future<T> future; public MethodRequest(Servant<T> servant, Future<T> future) { } public void call() { try { T result = servant.call(); future.setResult(result); future.setDone(true); } catch (Exception e) { } } }
由于activation list只依赖于method request,已经有了method request,现在我们编写activation list
package frame.activationlist; import java.util.ArrayList; import java.util.List; import frame.methodrequest.MethodRequest; public class ActivationList { private List<MethodRequest> requestList = new ArrayList<MethodRequest>(); public synchronized void insertTask(MethodRequest request) { requestList.add(request); } public synchronized void removeTask(MethodRequest request) { requestList.remove(request); } public synchronized boolean isEmpty() { return requestList.size() == 0; } public synchronized MethodRequest popFirst() { MethodRequest e = requestList.get(0); requestList.remove(0); return e; } }
现在我们遇到了一点小问题,ActivationList在eclipse下会报黄色警告,因为MethodRequest是泛型的,里面含有返回值的类型信息。
但是对于ActivationList来说根本不会在意返回值类型,只要是method request都可以放入ActivationList中。如果将ActivationList修改成泛型类,显然也是不合理的。解决方法是:将MethodRequest替换成MethodRequest<?>。
package frame.activationlist; import java.util.ArrayList; import java.util.List; import frame.methodrequest.MethodRequest; public class ActivationList { private List<MethodRequest<?>> requestList = new ArrayList<MethodRequest<?>>(); public synchronized void insertTask(MethodRequest<?> request) { requestList.add(request); } public synchronized void removeTask(MethodRequest<?> request) { requestList.remove(request); } public synchronized boolean isEmpty() { return requestList.size() == 0; } public synchronized MethodRequest<?> popFirst() { MethodRequest<?> e = requestList.get(0); requestList.remove(0); return e; } }
下面我们编写scheduler角色
package frame.scheduler; import frame.activationlist.ActivationList; import frame.methodrequest.MethodRequest; public class TaskScheduler implements Runnable { private ActivationList safeRequestList = new ActivationList(); public TaskScheduler() { } public void insertRequest(MethodRequest methodRequest) { safeRequestList.insertTask(methodRequest); } @Override public void run() { while (true) { if (!safeRequestList.isEmpty()) { MethodRequest request = safeRequestList.popFirst(); request.call(); } } } }
现在编写scheduler类
package frame.scheduler; import frame.activationlist.ActivationList; import frame.methodrequest.MethodRequest; public class TaskScheduler extends Thread { private ActivationList safeRequestList = new ActivationList(); public TaskScheduler() { this.start(); } public void insertRequest(MethodRequest<?> methodRequest) { safeRequestList.insertTask(methodRequest); } @Override public void run() { while (true) { if (!safeRequestList.isEmpty()) { MethodRequest<?> request = safeRequestList.popFirst(); request.call(); } } } }
接下来是代理类,这个类应该符合客户端的调用习惯
package frame.proxy; import frame.future.Future; import frame.methodrequest.MethodRequest; import frame.scheduler.TaskScheduler; import frame.servant.Servant; public class ExecuteTask { private static TaskScheduler scheduler = new TaskScheduler(); public static <T> Future<T> execute(Servant<T> servant) { Future<T> future = new Future<T>(); MethodRequest<T> request = new MethodRequest<T>(servant,future); scheduler.insertRequest(request); return future; } }
最后我们进行测试
package frame; import frame.future.Future; import frame.proxy.ExecuteTask; import frame.result.WeatherResult; import frame.servant.WeatherServant; public class Test { public static void main(String[] args) { Future<WeatherResult> future = ExecuteTask.execute(new WeatherServant()); System.out.println("I am running."); System.out.println(future.get()); } }
经过DIP后,改代码已经能够实现复用。如果新增1个类似耗时的距离服务,那么仅仅需要编写对应的servant即可,而该类完全都是业务逻辑,不涉及active object框架性代码。