1.当我们不知道该如何去描述一个事物时,我们就需要对事物进行画图分析。这就要求我们多去观察生活中的事物,将他们看做是对象。
2.先写好该如何实现,需要几个事物,有几个事物就有几个对象。首先,红绿灯交通系统顾名思义就需要红绿灯,灯有亮和熄灭。并且一般路口处对面的灯是同时亮和熄灭的。
所以又要定义个相反的灯。都属于灯这个事物,所以我们第一个对象就是灯
3.当灯亮时,说明能通行了,那么车该往哪行驶呢? 就需要路这个事物,来指定车辆可以通行的方向。
如图所示,十字交叉路口是路口中最复杂的一种,但是有特点,即当从北向南的灯亮起时,从南向北的灯也会亮起。
这时,要减少程序的复杂度,我们将向右行驶的车辆全部设为绿灯。不需转换。
就可以把图中的路线改为八条,并且是对称的。当S2W可以行走时,也就表示N2E也可以行走。
就可以将需要确定的路线再一次减少为四组,当第一组亮起时,即两条线路亮起。
那么就开始定义一个灯的事物。此事物中包含对称的灯,还有用这十二条路线来表示这十二条路线的灯的命名。
其中需要十秒进行一次跳转,这里就需要对灯进行操作了,并且灯进行了改变后,车辆的路线也发生改变。所以即操作灯,又操作车的事物我们就抽象定义为系统
假设第一次亮起绿灯的是S2N这条路线,那么N2S也会亮起灯,当过了一段时间后,这两个灯熄灭,再去进行下一个灯的亮起,然后再熄灭再进行。依次重复。
其中,灯亮起时,车辆进行通行,说起来是车在通行,其实就是这条路可以通过,真正用到车这个事物的就是创建和消失,那么这个就可以把车给省略掉,从而改成使这条路上出现一个计数器,来随机生成车辆。这个随机生成车辆和红绿灯切换的时间都属于线程,这里用到线程池的概念。综上所述,真正需要的对象即路,灯,操作路和灯的系统
路:产生一个类似汽车的计数器,来合计创建并消失的该线路(12个)下的车辆的数量。并且使用线程技术
为什么要使用线程呢? 当我进行计数时,为了防止计数的错误,使用了线程。
灯:因为一共有十二条线路,所以需要控制这十二条线路的话就需要十二个灯,这里使用十二条路线的名称来命名灯,就可以用枚举。
新技术产生的目的:使程序开发更简便,完整性更高
当一个灯亮起时,另一个灯也随之亮起,那么就把对应的灯也作为参数。方便操作。假设灯亮为true,灯熄为false,就需要一个bool值来判断灯的状态
但是如果第二个参数是灯这个类对象的话,因为枚举也会定义第二个参数那样的类,第二个如果还没有定义的话难免会发生错误,所以这里我们使用的是灯的名称,即String类型的名称传入进去。进行操作。并且有亮灯方法,以及获取下一个灯的方法,便于实现自动转换灯。
所以灯的代码如下:
public enum Lamp {
/*每个枚举元素各表示一个方向的控制灯*/
//为什么枚举里面的参数如此定义呢? 枚举的第一个参数是相反的灯,而第二个参数则是下次进行亮起的灯,第三个参数则是设置灯的默认状态为熄灭
S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),
/*下面元素表示与上面的元素的相反方向的灯,它们的“相反方向灯”和“下一个灯”应忽略不计!*/
//由于对称关系,下面的灯不需要再设置参数,直接可以随着上述的四个灯对象进行同步熄灭或者亮起。
N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),
/*由南向东和由西向北等右拐弯的灯不受红绿灯的控制,所以,可以假想它们总是绿灯*/
//这四个灯则不受红绿灯控制,假设一直绿着
S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);
//因为这些对象的内部已经设置好了参数,所以为了避免不必要的修改,将构造私有化。
private Lamp(String opposite,String next,boolean lighted){
this.opposite = opposite;
this.next = next;
this.lighted = lighted;
}
/*当前灯是否为绿*/
private boolean lighted;
/*与当前灯同时为绿的对应方向*/
private String opposite;
/*当前灯变红时下一个变绿的灯*/
private String next;
//返回灯的状态
public boolean isLighted(){
return lighted;
}
/**
* 某个灯变绿时,它对应方向的灯也要变绿
*/
public void light(){
this.lighted = true;
//上述对应的灯都已设为null,并且已将字符串传入到了对象的第二个参数中。
if(opposite != null){
//枚举的valueOf方法,返回一个对象,该对象的名称由字符串传入
//让对应的灯对象也调用light方法,由于第二个对象并没有第二个参数,为null所以不会再次进行这个方法的递归。
Lamp.valueOf(opposite).light();
}
//此处的六个指的是灯以及对应的灯还有另外四个不受控制的灯
System.out.println(name() + " lamp is green,下面总共应该有6个方向能看到汽车穿过!");
}
/**
* 某个灯变红时,对应方向的灯也要变红,并且下一个方向的灯要变绿
* @return 下一个要变绿的灯
*/
public Lamp blackOut(){
this.lighted = false;
if(opposite != null){
Lamp.valueOf(opposite).blackOut();
}
//如果熄灭了之后就要去调用第三个参数去创建对象,进行新的一轮操作。必须在外面先设null,这样就保证枚举中的对象只有前四个可以进行这个循环操作
//如果不设的话程序会停止。
Lamp nextLamp= null;
if(next != null){
nextLamp = Lamp.valueOf(next);
System.out.println("绿灯从" + name() + "-------->切换为" + next);
nextLamp.light();
}
return nextLamp;
}
}
当灯亮起后,则会产生一个个随机创建的汽车,然后消失。这个就使用路这个事物来进行描述。
public class Road {
//定义一个集合,去存储汽车
private List<String> vechicles = new ArrayList<String>();
private String name =null;
//构造,传入汽车的名称,命名方式:车辆通行的路的方向
public Road(String name){
this.name = name;
//模拟车辆不断随机上路的过程
//使用线程池
//创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行
所以,这个线程池的作用是:控制线程的个数
ExecutorService pool = Executors.newSingleThreadExecutor();
//匿名内部类的形式传入一个参数,并复写run方法
pool.execute(new Runnable(){
public void run(){
for(int i=1;i<1000;i++){
try {
//延迟随机1-10秒,需要处理异常
Thread.sleep((new Random().nextInt(10) + 1) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//添加汽车,并以计数器来进行区分和计数
vechicles.add(Road.this.name + "_" + i);
}
}
});
//每隔一秒检查对应的灯是否为绿,是则放行一辆车
newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
//该线程池的作用:定时,周期性执行
//此处传递了1这个参数,将线程数量设置为1ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run(){
//当集合中存在车时,将按照路中的名字来进行判断是否对应名称的灯亮
if(vechicles.size()>0){
boolean lighted = Lamp.valueOf(Road.this.name).isLighted();
//如果亮了,就将其从集合中删除,并且返回该元素打印字符串。
if(lighted){
System.out.println(vechicles.remove(0) + " is traversing !");
}
}
}
},
1, //定时一秒
1, //之后都为一秒进行周期性执行
TimeUnit.SECONDS);//计时单位,秒
}
}
接下来有了路和灯了,就可以来创建一个系统了
public class LampController {
//当前的灯
private Lamp currentLamp;
public LampController(){
//刚开始让由南向北的灯变绿;
currentLamp = Lamp.S2N;
currentLamp.light();
/*每隔10秒将当前绿灯变为红灯,并让下一个方向的灯变绿*/
//计时线程池,周期性执行
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run(){
//将灯熄灭后,返回下一个亮起的灯
currentLamp = currentLamp.blackOut();
}
},
10, //定时为10秒
10, //周期性为10秒
TimeUnit.SECONDS);
}
}
接下来就可以编写main方法来进行验证
public class MainClass {
/**
* @param args
*/
public static void main(String[] args) {
/*产生12个方向的路线*/
String [] directions = new String[]{
"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"
};
for(int i=0;i<directions.length;i++){
new Road(directions[i]);
}
/*产生整个交通灯系统*/
new LampController();
}
}