交通灯管理系统
1.面向对象设计经验:
面向对象设计把握一个重要的经验:谁拥有数据,谁对外提供操作这些数据的方法
实例:
通过反射获取类的方法中曾经提到过:
①人在黑板上画圆
对象:Person Blackboard Circle
要想画出圆->需要知道point,radius->这些数据是圆所有->因此画圆的方法由圆提供
②人关门
Person Door
closeDoor应该定义在Door中,这是因为人只是对门发出一个关门的信号,具体怎么关的门
由门自己完成(旋转,弹簧压缩,锁插入锁眼等等)
③售票员统计收获小票的金额
countTotalMoney()方法应该定义在小票内部,因为小票上才有金额
④球从一根绳子的一端移动到另一端:
class Rope{
private Point start;
private Point end;
public Rope(Point start,Point end){ //需要知道绳子的起点和终点(坐标形式)
this.start=start;
this.end=end;
}
public Point nextPoint(Point currentPoint){
.....
}
}
class Ball{
private Rope rope;//哪根绳子
private Point currentPoint;//球当前在绳子上的位置
private(Rope rope,Point startPoint){//球的起始点
this.rope=rope;
currentPoint=startPoint;
}
public void move(){//小球移动由自身发出
currentPoint=rope.nextPoint(startPoint);
System.out.println("小球移动到了"+currentPoint);
}
}
⑤两块石头磨成一把石刀,石刀可以砍树,砍成木材,木材做成椅子
其中一种思想:
StoneKnife sk=KnifeFactory.createKnife(Stone first,Stone Second)//磨刀的动作定义在stone和stoneknife中均不合适
Stone
StoneKnife
Wood wood=StoneKnife.cut(Tree tree)//砍的动作由StoneKnife发出
Tree
Wood
Chair chair=ChairFactory(Wood wood)
Chair
2.交通灯管理系统:
模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:
1.异步随机生成按照各个路线行驶的车辆
例如:
由南而来去往北向的车辆---直行车辆
由西而来去往南向的车辆---右转车辆
由东而来去往南向的车辆---左转车辆
.....
2.信号灯忽略黄灯,只考虑红灯和绿灯
3.应考虑左转车辆受信号灯控制,右转车辆不受信号灯控制
4.具体信号灯控制逻辑与现实生活中普通灯控制逻辑相同,不考虑特殊情况下的控制逻辑
注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆
5.每辆车通过路口时间为1秒(可通过sleep模拟)
6.随机生成车辆时间间隔 以及 红绿灯交换时间间隔自定,可以设置7.不要求实现GUI,只考虑系统逻辑实现,可通过log方式展现程序运行结果
示意图,该图出自黑马论坛的赵晓东同学,可以说这个图把灯切换过程都画了出来,非常清晰:
该图画出了其中一种各条路线变换的过程.
在交通灯管理系统中涉及到的对象:路线,汽车,交通灯,交通灯管理系统
汽车看到 自己所在的路线对应的灯绿了就穿过路口吗?不是,还需要看当前路线,我前面是否有车,只有我前面的车开始走了,我才能走.(队列)
看前面是否有车,需要问路,路中存储车辆的集合,这里不在把车单独描述成一个类
因为这里不是关注车怎么移动,只需要体现出车辆穿过路口即可,该路线上减少了一辆车.
(类比之前说的谁拥有数据,谁对外提供操作这些数据的方法,在这里车辆相当于路线上的数据,由路线来提供增减车辆的方法)Road类:
package com.itheima.trafficlampsystem; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class Road { private String roadName;//路的名称 private List<String> vehiles=new ArrayList<String>();//用于装路线上的车辆 public Road(String roadName){ this.roadName=roadName; /*单独线程操作增加车辆*/ Executors.newSingleThreadExecutor().execute(new Runnable(){//单独使用一个含有一个线程的线程池来 //每隔一段时间生成车辆 @Override public void run() { for(int i=1;i<=1000;i++){ try { Thread.sleep((new Random().nextInt(10)+1)*1000);//假设1~10秒不定时间路线上添加车辆 } catch (InterruptedException e) { //让每条路线上的线程都有执行到的机会 // TODO 自动生成的 catch 块 e.printStackTrace(); } vehiles.add(Road.this.roadName+"--"+i);//路名+标号构成车辆名 } } }); /*单独线程操作减少车辆*/ Executors.newScheduledThreadPool(1).scheduleAtFixedRate( //红绿灯在路线上的,每条路线与各自灯绑定, //由路线来时刻关注红绿灯变化状态 new Runnable(){ //当变成绿灯的时候,路线便移除在改路线上的第一辆车(表示它通过路口) @Override public void run() { if(vehiles.size()!=0){//如果该路上没车不再remove boolean lighted= TrafficLamp.valueOf(Road.this.roadName).isLighted();//根据路名来 if(lighted) //获取当前灯的状态 System.out.println(vehiles.remove(0) +"...travel crossing!");//试想车辆排成一个队列,当灯为绿灯时, //不断经过路口的均是队头车辆 //要想判断当前路线上灯的状态还需要借助于灯管理系统 } } }, 1,//delay 1, //period TimeUnit.SECONDS);//1秒后判断是否有车并且是否为绿灯,是开始移除队头车辆,之后每隔1秒后再次执行该任务 } } /*当我们在创建一个路线对象时同时使用两个线程池(内部均只有一个线程)各自独立控制车辆的增加和减少*/TrafficLamp类:
package com.itheima.trafficlampsystem; /* 一共有12条路线,每条路线均有一个交通灯,也就是12个灯对象(枚举常量) 其中右转(4盏),其余8盏(两两相对) */ enum TrafficLamp{ /*在写next:满足同方向先直行后左转,然后下一个同方向先直行后左转*/ S2N(false,"N2S","S2W"),S2W(false,"N2E","E2W"),//2代表to(4代表for),不止一种写法 W2E(false,"E2W","W2N"),W2N(false,"E2S","N2S"), N2S(false,"S2N","N2E"),N2E(false,"S2W","W2E"), E2W(false,"W2E","E2S"),E2S(false,"W2N","S2N"), S2E(true,null,null),N2W(true,null,null),//各个路口的右拐灯恒绿,没有对应的灯和下一盏灯 W2S(true,null,null),E2N(true,null,null); private boolean lighted;//当前灯的状态 true为绿灯,flase为红灯 private String opposite;//该灯对应的灯(同红同绿的灯),这里不使用TrafficLamp类型,例如:S2N(N2S) //N2S还没有定义(不能先使用后定义) private String next; //该灯对应的下一盏要改变状态的灯,也就是直行后的左转灯 private TrafficLamp(boolean lighted,String opposite,String next){//初始化以上三个成员变量 this.lighted=lighted; this.opposite=opposite; this.next=next; } public boolean isLighted(){//对外提供一个获取当前灯的状态 return lighted; } public void turnGreen(){//当前灯的状态变成绿灯,同时对应的灯也变成绿灯 lighted=true; //例如:N2S为绿->S2N为绿,N2E为红 System.out.println(name()+"...变绿"); if(opposite!=null) TrafficLamp.valueOf(opposite).lighted=true; System.out.println(TrafficLamp.valueOf(opposite).name()+"...变绿"); /* if(next!=null){ //这个可以不用,因为初始值的lighted均为false TrafficLamp.valueOf(next).lighted=false; TrafficLamp.valueOf(TrafficLamp.valueOf(next).opposite).lighted=false; }*/ } public TrafficLamp turnRed(){//当前灯的状态变成红灯 lighted=false; System.out.println(name()+"...变红");//以下打印语句均为调试用 if(opposite!=null)//右拐灯opposite==null TrafficLamp.valueOf(opposite).lighted=false; System.out.println(TrafficLamp.valueOf(opposite). name()+"...变红"); TrafficLamp nextLamp=null; if(next!=null){//不但需要把下一盏灯变绿,还要把下一盏灯的oppsite对应的灯也要变绿 nextLamp=TrafficLamp.valueOf(next);//next不为null才能valeOf nextLamp.lighted=true; System.out.println(nextLamp.name()+"...变绿"); TrafficLamp.valueOf(nextLamp.opposite).lighted=true; System.out.println(TrafficLamp.valueOf(nextLamp.opposite). name()+"...变绿"); } return nextLamp;//会记录下一盏变绿的灯时用到 } } /* 视频中的另外一种做法: 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), 为了防止下面递归进入死循环. public void turnRed(){//当前灯的状态变成红灯 lighted=false; if(opposite!=null)//右拐灯opposite==null TrafficLamp.valueOf(opposite).lighted=false; if(next!=null){ TrafficLamp.valueOf(next).turnGreen(); //可以做到把下一盏灯变绿,还会把下一盏灯的oppsite对应的灯也变绿 } } 这样做会使初始的灯赋值有些限制,下面会提到 */LampControlSysteml类:
package com.itheima.trafficlampsystem; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /*该控制系统主要用来协调,控制灯的切换动作*/ public class LampControlSystem { private TrafficLamp currentGreenLamp;//记录当前绿灯 public LampControlSystem(){ currentGreenLamp=TrafficLamp.S2N;//如果用TrafficLamp使用递归,你不能使用 //oppsite和next均为null的路线,因为无法完成切换 //不使用,可以任意选一条路线 currentGreenLamp.turnGreen();//把S2N路线上的灯置为绿灯 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( new Runnable(){ @Override public void run() { currentGreenLamp=currentGreenLamp.turnRed();//将当前绿灯切换为红灯, //同时记录nextLamp(下一次变绿的灯),很巧妙 } }, 10, 10, TimeUnit.SECONDS);//定义一个计时器每隔10秒一切换 } } /* 以初始灯为S2N为绿灯示例:(结合程序说明示意图中的情况一->情况四的灯变化) 初始状态所有灯均为红灯 ① S2N->S2N.turnGreen();->S2N 绿 ,N2S 绿,S2W 红,N2E 红 ② 10秒后->S2W=S2N.turnRed();->S2N 红,N2S 红,S2W 绿,N2E 绿 ③ 10秒后->E2W=S2W.turnRed();->S2W 红,N2E 红,E2W 绿,W2E 绿 ④ 10秒后->E2S=E2W.turnRed();->E2W 红,W2E 红,E2S 绿,W2N 绿 ...... */MainClass类:
package com.itheima.trafficlampsystem; public class MainClass { /** * @param args */ public static void main(String[] args) { // TODO 自动生成的方法存根 String[] directions={ "S2N","S2W","N2S","N2E","E2W","E2S","W2E","W2N","W2S","E2N","S2E","N2W" }; for(int i=0;i<12;++i) new Road(directions[i]);//每条路线上均有两个线程池(每个线程池中一个线程)分别控制增减车辆 new LampControlSystem(); } }在每次灯切换前,至多只有六个方向上的车驶过(该路线上没车或者还没切换到该路线减少车辆线程灯已经开始变化都可能没打印出来):例如: S2N绿 N2S绿包括六个方向: S2N,N2S(直行) S2E,N2W,W2S,E2N(右转)
3.回顾与总结:
难点:
面向对象的分析与设计,感觉如何把问题以面向对象的角度去分析和设计
对于没有经验的我来说相当难
主要用了哪些知识点:
枚举(enum),线程池(ExecutorService),计时器,设计技巧等
Road类:
12个Road对象,每个Road对象使用两个线程池(都只有一个线程)
分别控制产生当前路线上的车辆(由于线程切换执行达到随机生成不同路线上的车辆),
队头车辆驶过路口(每次移除该路线队头车辆)
TrafficLamp类:
12条路线对应12个灯对象,将每盏灯对应的灯变绿或变红,以及下一盏灯变红或变绿
LampControlSystem类:
相当于启动初始的一个交通灯,循环控制切换每盏灯的状态