您的位置:首页 > 职场人生

黑马程序员——交通灯管理系统总结

2014-10-01 21:03 183 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

看了张孝祥老师的交通灯管理系统的视频,获益良多。尤其里面的多线程和枚举的用法让人印象深刻,而且视频里面的设计思路深刻体现了面向对象的思想。可以说代码不多,都是精华。这里把我看完视频以后收获的东西总结一下,加深理解。

交通灯管理系统的需求如下:

1.异步随机生成按照各个路线行驶的车辆;

2.信号灯忽略黄灯,只考虑红灯和绿灯;

3.应考虑左转车辆控制信号灯,右转车辆不受信号灯控制;

4.具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑;

5.每辆车通过路口时间为1秒;

6.随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置;

7.不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

张老师在解决实现这个系统的时候,主要用到了以下的知识点:

1.线程池和线程定时任务

2.枚举

3.匿名内部类

下面详解张老师解决问题的思路和步骤:

第一步,张老师明确了这个系统需要哪些类来协同实现。这一步,张老师用面向对象的思想,熟练的从需求中抽象出下面几个类:

LampController,用来控制灯的变化

Lamp,用来表示交通灯,这个类设计的很巧妙,也是这个系统比较难理解的部分,下面会详细介绍这个类的功能;

Road,用来表示在这个需求中十字路口的12种不同方向的路线。

MainClass,用来做整个系统的入口

第二步,张老师先完成了需求中对于车辆的产生,以及车辆用规定的时间通过路口的设置。这一步是用Road类来实现的,每个Road类的实例都代表十字路口一个方向的路线。而Road类有一个成员变量,叫 vechicles,代表的是这个方向上产生的车辆集合。这里用到的知识点是线程池,作用是给对应方向Road对象的里的vechicles集合中,在1到10秒的时间段内,随机的产生车辆加入到这个方向的vechicles集合中。到这里,需求里的第一点就完成了,代码如下:

<span style="font-size:14px;">ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(new Runnable() {
public void run() {
for (int i = 1; i < 1000; i++) {
try {
Thread.sleep((new Random().nextInt(10) + 1) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
vechicles.add(Road.this.name + "_" + i);
}
}
});</span>


Road类的对象还有一个作用,就是规定让产生的车辆用1秒的时间通过路口,也就是说,如果灯是绿灯,可以通行的话,一秒以后,Road类的vechicles集合中,就要去掉一个元素,表示这个元素已经通过了这个十字路口。这里用到的知识点是ScheduleExecutorService定时周期执行任务。代码如下:

<span style="font-size:14px;">ScheduledExecutorService 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);</span>


这段代码的实现,完成了需求中的第五点。到目前为止,整个Road类的实现就已经完成,全部的代码如下:

<span style="font-size:14px;">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();
pool.execute(new Runnable() {
public void run() {
for (int i = 1; i < 1000; i++) {
try {
Thread.sleep((new Random().nextInt(10) + 1) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
vechicles.add(Road.this.name + "_" + i);
}
}

});

ScheduledExecutorService 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);

}
}</span>


其中成员变量name表示的是这个Road对象所代表的方向路线。

第三步,张老师设计了这个系统的核心类,也是最精华的枚举类Lamp,用来代表交通灯,下面来详细介绍下这个类的实现思路。

首先,这个类是枚举类的,里面的每个元素描述的并不是我们通常意义上的四个方向的交通灯,而是12个方向对应的灯的状态,在这里对12个方向的路灯状态分别描述,对后面我们的代码逻辑有很大的简化作用。(枚举技术是5.0以后出现的特性,平时在代码中也不是特别常用这个特性,但是在这里用的恰到好处,简化了代码,可见对新技术的掌握和使用是保持程序员竞争力的有力手段)

其次,这个类有三个成员变量,描述的是Lamp这个类对象的三个关键属性:灯的红绿状态(用Boolean类型变量,绿是true,红是false),灯的正对的路灯(String类型变量,描述对面路灯的名称。因为正对方向的灯是同红同绿的,这样能方便同时操作),路灯对应的下一个要变化状态方向的灯(String类型的变量,相邻的灯的交替红绿的,相对的灯是同时红绿的,明确这个变量的名字可以告知LampController类怎么控制交通灯的变化)。

最后,这个类提供三个对外调用的方法,1.返回路灯当前红绿状态;2.让路灯变绿,同时操作正对方向的路灯变绿;3.让路灯变红,同时操作正对方向的路灯变红,并且让相邻的灯变绿。

这个类的元素的定义需要重点说明,因为在需求中,右转车辆不受交通灯控制,可以直接通行,所以在定义的时候右转的四个方向灯的状态都是默认是绿的;不管是左转的方向还是右转的方向,都是不存在正对方向的,并且控制相邻灯变化只需要定义4个正对的方向就够了,因此左转右转的的8个方向正对的成员变量和下一个要变化路灯的成员变量都不需要赋值。

示例图如下——



最后这个枚举类的12个元素就定义成下面这样:

<span style="font-size:14px;">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);</span>
EWSN代表东西南北,S2N代表南向北,一次例推。
最后这个类就设计成了这样:

<span style="font-size:14px;">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 boolean lighted;
private String opposite;
private String next;

private Lamp(String opposite, String next, boolean lighted)
{
this.opposite = opposite;
this.next = next;
this.lighted = lighted;
}

public boolean isLighted() {
return lighted;
}

public void light() {
this.lighted = true;
if (opposite != null) {
Lamp.valueOf(opposite).light();
}
System.out.println(name() + " lamp is green");
}

public Lamp blackOut() {
this.lighted = false;
if (opposite != null) {
Lamp.valueOf(opposite).blackOut();
}
Lamp nextLamp = null;
if (next != null) {
nextLamp = Lamp.valueOf(next);
System.out.println("绿灯从" + name() + "切换为------->"  + next);
nextLamp.light();
}
return nextLamp;
}
}</span>


这个类的完成,2,3,4三个需求功能点已经完成。

第四步,张老师设计了一个交通灯的控制类,用来控制交通灯在固定的时间里交替变化状态。这个类也用到了ScheduledExecutorService的线程定时调度功能。这个类在最开始先将S2N这个方向的交通灯变绿,之后每10秒调用一次关灯的方法,关灯的方法会在关灯的同时开启相邻的交通灯,于是程序就不停的运行下去。

这个类设计出的代码如下:

<span style="font-size:14px;">public class LampController
{
private Lamp currentLamp;

public LampController()
{
currentLamp = Lamp.S2N;
currentLamp.light();

ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("灯变化");
currentLamp = currentLamp.blackOut();
}
}, 10, 10, TimeUnit.SECONDS);
}
}</span>

这个类的完成,第六个需求点也完成了。至此,这个系统已经完成。

最后我们来总结下张老师在整个系统中用到的几个知识点的细节:

1.张老师总共使用了两种形式的线程池服务,第一种,newSingleThreadExecutor,这种方法创建出的线程是单例的,同一时间只有一个线程被创建,当一个线程结束的时候,另一个线程会被开启。用这中方法来生成汽车可以避免汽车生成的太快。第二种,newScheduledThreadPool,这种方式生成的线程池是调度型的线程池,有固定的周期。在汽车固定1s通过路口和交通灯每10秒变化一次的地方,张老师用了这种线程池。

2.张老师在定义交通灯这个类的时候用了枚举类,在最开始定义的时候就把12个方向路灯的关系描述的清楚明白。后面在调用对应方向的路灯的时候直接用枚举类的valueOf方法,简单明了。省去了复杂的交通灯的初始化逻辑,让12个方向的交通灯在代码中直接使用。这个地方枚举的用法恰到好处,让人印象深刻。

3.张老师在使用线程池的时候多次使用了匿名内部类,定义的格式是 new 接口名() {/*实现代码*/},

内部类调用外部类的对象的成员变量的时候,调用方法是:外部类名.this.外部类成员变量名
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: