0. OO原则
谁有数据,谁有方法。
最典型的问题:人在黑板上画圆。其中涉及3个类,人-Person类,黑板-BlackBoard类,圆-Circle类。画圆动作到底是属于谁?应用上面原则,发现圆最重要的两个数据,一个是圆心,另一个是半径,它们同属于圆这个类,而画圆就是基于这两个数据来进行的,所以画圆应该是圆的方法。这与我们最本质的认识是有区别的,因为明明是人画的圆啊。此处可以这样理解,在面向对象中最重要的是每个单独的类,另一个非常重要的是各个类之间的关系,它们之间存在着相互的调用关系,人调用了画圆的方法。
1. 项目需求
模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:
1)异步随机生成按照各个路线行驶的车辆(多个线程控制)。
例如:
由南向而来去往北向的车辆 —- 直行车辆
由西向而来去往南向的车辆 —- 右转车辆
由东向而来去往南向的车辆 —- 左转车辆
。。。
2)信号灯忽略黄灯,只考虑红灯和绿灯。
3)应考虑左转车辆控制信号灯,右转车辆不受信号灯控制(抽象为常绿的灯)。
4)具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。
注:
1)南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。
2)每辆车通过路口时间为1秒(提示:可通过线程Sleep的方式模拟)。
3)随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。
4)不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
2. 系统分析
根据项目需求可以画出下面示意图(越是复杂的东西越要学会用图表示,直观)(
PS:为了更好地完成此项目,连续两天晚上都回去我们学校旁边的岱宗大街+康复路与龙潭路十字路口观察,现在对此已十分了解)
由上图可以看出系统有12条路线(N2W、E2N、S2E、W2S...)组成,每条路线对应一个交通灯,每个灯有红、绿(忽略黄色),当灯为绿时,该灯对应的路线上可以有汽车通过。
3.系统设计
通过上方的分析可以看出,其实有三个类在工作,分别道路Road类、交通灯Lamp类、交通工具Vehicle类(此处省略此类,用字符串代替)。
当把上面12条路线抽象出一条路线,整个系统的架构以及各个模块之间的关系应该是如下图所示:
4.详细设计及编码
其中注释就是详细设计的过程。
1)Lamp类
package tad.blog.traffic;
/**
* 交通灯类
*
* 因为灯的个数是确定,所以应用枚举解决。
* 每条路线应该有一个灯控制,共12条路线,所以应该有12个灯,即12个枚举元素。
* 因为每次同时进行变化的都是逆方向变化,所以可以用一个灯来代表一组灯简化系统设计。
* 分组如下,只需要控制每组灯中的一个即可。
* S2N,N2S
* S2W,N2E
* E2W,W2E
* E2S,W2N
* 因为右转不受控制,所以修定绿灯常量简化问题。即从南向东和从西向北及对应方向不受红绿灯的控制,会做特殊处理。
* S2E,N2W
* E2N,W2S
*
* @author tad http://weibo.com/weinabanta
*/
public enum Lamp {
//每个元素表示一个交通灯
//项目要求右拐路线不受红灯控制,可假想总是绿灯来简化问题
S2E(true,null,null),N2W(true,null,null),E2N(true,null,null),W2S(true,null,null),
//最主要的四个灯,其它灯的变化都是围绕此展开。
S2N(false,"N2S","S2W"),S2W(false,"N2E","E2W"),E2W(false,"W2E","E2S"),E2S(false,"W2N","S2N"),
//下面的灯方向与上面对应灯相反,因为灯的变化一样,所以“相反方向灯”和“下一个灯”设为null,但不影响使用,因为通过上面4个灯可以把所有灯都联系起来。
N2S(false,null,null),N2E(false,null,null),W2E(false,null,null),W2N(false,null,null);
//因只考虑红、绿两种状态,故可用boolean类型表示灯的状态:true为绿灯,false为红灯。
private boolean status;
//与当前灯同时为绿的对应方向。
private String opposite;
//当前灯变红时下一变绿的灯。
private String next;
//构造器
private Lamp(boolean status,String opposite,String next){
this.status = status;
this.opposite = opposite;
this.next = next;
}
//返回当前灯的状态
public boolean getStatus(){
return status;
}
//灯变绿
public void toGreen(){
this.status = true;
System.out.println(name() + " 变绿,总共会有6个方向汽车穿过...");
if(opposite != null){
Lamp.valueOf(opposite).toGreen();
}
}
//灯变红
public void toRed(){
this.status = false;
if(opposite != null){
Lamp.valueOf(opposite).toRed();
}
}
//下一变绿的灯
public Lamp next(){
if(next != null){
return Lamp.valueOf(next);
}
return null;
}
}
2)Road类
package tad.blog.traffic;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 每个Road对象代表一条路线,总共有12条路线,即系统中总共要产生12个Road实例对象。
* 每条路线上随机增加新的车辆,增加到一个集合中保存。
* 每条路线每隔一秒都会检查控制本路线的灯是否为绿,是则将本路线保存车的集合中的第一辆车移除,即表示车穿过了路口。
* @author tad http://weibo.com/weinabanta
*/
public class Road {
//路名字
private String name;
//路上的车,不必设为private
private ArrayList<String> vehicles = new ArrayList<String>();
//构造器
public Road(String name) {
this.name = name;
//使用线程池中线程模拟有车不断上路,随机的。
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(new Runnable(){
public void run() {
for(int i = 0;i< 9999;i++){
try {
Thread.sleep((new Random().nextInt(10) + 1) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//访问外部类的字段
vehicles.add(Road.this.name + "_" + i);
}
}
});
//使用ScheduledExecutorService线程池中的线程每隔一秒检查相应的灯是否为绿,如果是则放行一辆车
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run() {
if(vehicles.size()>0){
boolean status = Lamp.valueOf(Road.this.name).getStatus();
if(status){
//移除顶上的车
System.out.println(vehicles.remove(0) + " is traversing !");
}
}
}
},
1,
1,
TimeUnit.SECONDS);
}
}
3)LampController类
整个系统的控制中心,掌握所有灯的变化。
package tad.blog.traffic;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 因为LampController在整个系统中只能有一个,使用设计模式中的单例模式,还是饿汉模式。
* @author tad http://weibo.com/weinabanta
*/
public class LampController {
private Lamp currentLamp;
private static LampController controller = new LampController();
//当前灯
private LampController(){
this.currentLamp = Lamp.S2N;
}
//返回实例
public static LampController getInstance(){
return controller;
}
//开始运行此交通灯系统
public void start(){
//将当前灯变绿
currentLamp.toGreen();
//再次使用到ScheduledExecutorService线程池,启动定时器,每隔10秒将当前灯变红,并将下一个灯变绿,也正因为此所有灯都变化起来了。
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run() {
//完成灯的切换
System.out.println("绿灯从 " + currentLamp.name() + " --------> 切换为 " + currentLamp.next().name());
currentLamp.toRed();
currentLamp = currentLamp.next();
currentLamp.toGreen();
}
},
10,
10,
TimeUnit.SECONDS);
}
}
4)Boss类
把所有类整合到一起,系统完成。
package tad.blog.traffic;
/**
* 把所有类整合起来
* @author tad http://weibo.com/weinabanta
*
*/
public class Boss {
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]);
}
//得交通灯控制器,调用方法使整个系统动起来。
LampController.getInstance().start();
}
}
5.项目总结
1)真正做好一个项目是需要建立对业务流程的熟练、准确掌握,这是最根本的,此处只有真正了解了红绿的调度过程才能真正做此系统,这也是我为什么连续两天去十字路口观察的原因。
2)面向对象,包括OOA、OOD、OOP需要大量练习才能真正掌握,而这也是成为一个优秀程序员的根本。此项目其实并不是完全面向对象的,比如开走车的方法不应该放在Road中。
3)设计模式一定要掌握,可以解决太多问题,比如此项目用了单例模式,保证了整个项目只有一个LampController。
4)对Java语法及核心类库要熟练掌握。此处用到多线程、枚举、内部类等相对高级的技术。