从一个简单的计时器,看Java接口的作用

时间:2023-01-12 20:52:09

作为一个Java初学者来说,应该很难理解 interface 的作用,明明是个没有实现任何功能的东西,在API中却有那么多的接口类型,下面我将会用一个很小的计时器例子来简单展示下 interface 作用。

类库很简单,只有三个元素,类图如下:

从一个简单的计时器,看Java接口的作用
从一个简单的计时器,看Java接口的作用
从一个简单的计时器,看Java接口的作用

Timer:是个计时器

ExecutionThread:是在计时器内使用的线程

ITask:即为计时器要执行的任务,而UserTask将会是用户为了给计时器指派任务而实现的接口

ITask 接口源码


package individual.hcx.generictimer;

/**
 * 通用计时器的任务接口,为计时器指派任务,只需实现此接口中的task(),并用实现类初始化计时器即可
 * @author hcx
 *
 */
public interface ITask {
	void task();
}

 
 

该接口很简单,只有一个方法需要实现,但该接口非常重要,后面会详细解释。

ExecutionThread源码

package individual.hcx.generictimer;

public class ExecutionThread extends Thread {

	//线程休眠事件
	private long period;
	
	ITask task = null;
	
	private boolean isRun = true;
	
	/**
	 * 创建一个线程休眠事件为 period 毫秒的线程
	 * @param period
	 * @param task
	 */
	public ExecutionThread(long period , ITask task)
	{
		this.period = period;
		
		this.task = task;
	}
	/**
	 * 结束线程执行
	 */
	public void stopTask()
	{
		this.isRun = false;
	}
	
	public void run() {
		try
		{
			while(isRun)
			{
				//执行
				task.task();
				//当前线程休眠
				Thread.sleep(period);
			}
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}
	}
	
}

该线程类的实现也很容易看懂,其中加了一个状态字,以控制线程的终止。

在这个执行线程中,最为重要的就是在run()方法中执行的task.task();这其实就是面向接口编程的一种方式,准确来说,在编写ExecutionThread代码的时候,我们仅仅只知道一个抽象的方法,但是我们却能够调用它。在ExecutionThread中我们其实并不关心task的具体实现,我们只要知道他有这样的方法就行了,这就是抽象的具体应用。

Timer的源码

package individual.hcx.generictimer;

public class Timer {
	long period = 0;
	
	ExecutionThread executionThread = null;
	/**
	 * 使用计时器的执行间隔初始化一个计时器
	 * @param period	执行间隔,以毫秒为单位,该值最小为1,如果传入小于1的值将还会是1
	 * @param task 计时器中执行的任务
	 */
	public Timer(long period , ITask task)
	{
		if(period <= 0)
			this.period = 1;
		else
			this.period = period;
		
		this.executionThread = new ExecutionThread(period, task);
	}
	
	/**
	 * 开始计时器,该方法只能调用一次
	 */
	public void start()
	{
		this.executionThread.start();
	}
	
	/**
	 * 结束计时器,一旦结束将无法重启,该方法只能调用一次
	 */
	public void stop()
	{
		this.executionThread.stopTask();
	}
}

这段代码是Timer的主体实现,它定义了两个行为,start和stop,用于开始和结束计时器。Timer的构造器是需要一个ITask对象来初始化的,在这里我们还是不需要关心ITask的实现。

至此,其实我们的类库已经开发完毕,这个时候完全可以把我们的代码编译打包,生成一个Jar给别人使用,那别人如何使用呢?这里我写了一个场景类来模拟。

package individual.hcx.generictimer;

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Timer timer = new Timer(1000 , new MyTask());
		timer.start();
	}

}

class MyTask implements ITask
{
	int time = 1;

	public void task() {
		// TODO Auto-generated method stub
		System.out.println(time);
		
		time ++;
	}
	
}

首先,我们需要实现一个ITask的实现类,在这个实现类中我们需要给计时器指派任务,也就是实现task()方法,我的实现很简单,只是输出一个数字。有了ITask的实现类,我们可以通过这个实现类实例化一个Timer对象,然后将它启动,这个时候你会发现,计时器可以很好的工作。当然啦,你完全可以再ITask的实现类中实现更复杂的方法,甚至说,你可以创建更多的ITask的实现类,然后来实例化更多的Timer对象,而这个时候你可以完全不用关心Timer的内部结构!

那么,interface 在这个程序中到底起了什么作用呢?功能隔离和编码分工!

计时器的功能是,能够以一定的周期执行某个特定的任务,一定的周期我们可以用一个long的变量来指定,但是特定的任务我们怎么处理呢?没错,就是用 interface 来抽象。于是,计时器的功能与具体的任务充分隔离开来了,既然功能隔离了,那么编码自然就可以分工了。负责编写计时器的的人,可以完全只去考虑计时器部分的实现。而实现指定任务的人,可以完全只去考虑任务的设计。只要这两拨人,遵循这个接口,那么在Client这样的高层结构中就能很好的集成,以完成一个复杂系统的开发。

最后,这个简单的计时器,其实可以理解为一个非常简单的框架,它通过接口来约束,客户在框架中的行为。当然啦,在实际中比较成功的框架,不仅仅只有接口这一个约束,但接口在任何一个框架中都是大量存在的!