一步步学习java并发编程形式之Active Object模式(四)改进后的java实现

一步步学习java并发编程模式之Active Object模式(四)改进后的java实现

         上一篇博客一步步学习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框架性代码。