黑马程序员---交通灯管理学习笔记

------- Java、.NetAndroid培训期待与您交流!------- 

一、交通灯管理系统的项目需求

二、交通灯路线分析图

        张孝祥劳老师在视频中已经详细的分析了对于交通灯的路线分析过程,12条路线。除了右转弯的4条路线不受红绿等控制外(为了编程方便,假设存在常绿的交通灯),其他路线都是两两相对(图中颜色标识了相对的路线的),所以南北走向为一组,东西走向为一组。这样我们在进行程序编码时,只需分两种情况来设计,即规定(假如 S2N)了一条通行(绿灯),那么将其相对路线(N2S)也掷为通行状态即可。对于反转向(S2W)的路线设置同样,但实际生活中反转向有转弯交通灯。这样的路线分析将是最简单的。如果采用其他方式都将增大信息处理量,不利于编码的实现。

三、面向对象的分析和设计

        面向对象设计把握一个重要的经验:谁拥有数据,谁就对外提供操作这些数据的方法。

        先发发牢骚吧,在进行面向对象分析时,惑一时,虽然现在明白点点,但是还是觉得太前沿了,有些超乎思维理解。就说人吃饭吧,其实吃的动作是属于人的行为,但是按照面向对象的思想来说,这个就错误了,因为吃这个方法在调用饭的属性,而人却没有这些属性。所以得把这个吃的方法定义在饭的描述上,这样才能实现调用,不管啥动物都可以调用。钻牛角尖确实不利于学习,以前绝得微积分和天文物理已经够抽象的了,但是直到学习了面向对象,才发现原来是小菜一碟。所以最后我觉得这句话能说服我,面向对象只是为了分析需求和方便编程以及提高功能模块独立化。程序员的思想就是超凡脱俗,不过为了学好它,我也就超凡脱俗一把,外物皆对象嘛!

        牢骚完了,正题:张老师在讲解时就该系统分成了三部,也就分成了三个对象。

  • 对于路和车的分析:每条路上车辆的增加都是随机的,且在绿灯期间减少1辆/s。难点在于对象分析路和车之间,至少我在开始分析该问题时把车(Vehicle)和路(Road)分成两个独立的个体的,将增加车辆和减少车辆的方法都封装在Vehicle 中,通过一个容器来完成增删操作。然后在设置路,但这时候出现了问题,那就是车已经把增删操作做完了,要路干嘛呢,直接把车和交通灯(Lamp)对应不就完了?但这样来还得调用Road 这个类的成员即路的走向(trendName),这样想来越来越麻烦。后来看了视频,把车看作是在Road 上的一个集合这样一下子就简单多了,通过构造方法启动一个线程来实现随机增加的车辆,在定义一个计时器每隔一秒对 Lamp的状态进行一次检查。这样就关联了路和灯,而车只是一个附属成员。而对于车的操作中没有对于车的属性需要定义的,也没有对于车的任何属性有过调用,所以就不应该存在车这个对象。 
  • 对于灯的分析:对于灯的分析相对容易,Lamp 的个数是确定的,即12盏交通灯,而且不能随意的增删该对象,而在我们所学的只是中也只有枚举有这个特性,即对数据管控的能力限定在一个范围内,不管什么操作,只能选择这么几个定义好的对象来操作,不能去 new 新对象。所以灯用枚举来定义,12个方向的灯即十二个Lamp 对象,其中有4个路口的灯状态为常绿,其它8盏灯是两两相对,只需定义其中的一盏,相对的灯同等变化。 
  • 灯的控制系统分析:这个对象在需求中是最不容易找到的,个人在分析时,从没把它归为一个单独的对象,而把其定义在Lamp 类中。但若定义在枚举中,那么这个灯的控制将受到灯这个对象的限制,也就是说换一个灯对象,那么这个控制方法将重新调用,不能连续控制,这不是我们需要的。所以需要定义个灯的控制系统LampController,用来控制灯的红绿状态转换。反正我是没想到这么个对象。

四、程序编码分析

  1、在整个系统编码中,涉及到一个重要的技术,即计时器。计时器的功能是在给的间隔时间段内,重复执行相同的动作。而计时器的定义涉及到线程池的创建,所以要用到 Executors 工具和接口 ScheduledExecutorService(计时器在该接口中通过方法 scheduleAtFixedRate 定义,一般都是通过匿名内部类的形式定义)。该系统中有三处用到了计时器:

  • 模拟车辆不断随机上路。我们假设最初灯的状态都是红色,这时所有路口都有车在等待,即对象一建立就有车存在。然后每隔1s~20s的随机时间向存储车辆的集合(模拟现实中的车辆先到先通行的原则,所以要使用LinkedList 集合,该集合中有操作集合头尾的方法,且该集合是有序集合)中新增车辆。
this.roadName = roadName;  
/*模拟车辆不断随机上路的过程*/ 
//newSingleThreadScheduledExecutor表示创建单线程的线程池
ScheduledExecutorService schedulePoolIn = newSingleThreadScheduledExecutor();
/*针对池创建一个计时器,起初全为红灯时每个路口都有车辆存在,之后每隔1~20s向集合中添加一辆车*/
schedulePoolIn.scheduleAtFixedRate(
                new Runnable() {
                        int count = 1;
                        @Override
                        public void run() {
                                vechicles.addFirst(Road.this.roadName + "_" + count++);
                        }
                }, 
                0, 
                new Random().nextInt(20) + 1, 
                TimeUnit.SECONDS);
  • 模拟在绿灯状态下每隔1s从集合中移除一辆车表示通过十字路口,且该计时器会每隔1s检查一次当前交通灯的状态,来判断是否需要移除对应路口集合中的车辆。
//每隔一秒钟检查一次路段的交通灯是否变绿,是则放行
ScheduledExecutorService schedulePoolOut = newScheduledThreadPool(1);
//针对池创建一个计时器,每隔1s从集合中remove一辆车通过路口
schedulePoolOut.scheduleAtFixedRate(
                new Runnable(){
                        @Override
                        public void run() {
                                if (vechicles.size() > 0) {
                                        boolean lighted = Lamp.valueOf(Road.this.roadName).isGreen();
                                        if (lighted) 
                                                System.out.println(vechicles.removeLast() + " is traversing...");
                                }
                        }
                }, 
                1, 
                1, 
                TimeUnit.SECONDS);

        上面两个计时器实现的功能都需要在 Road 对象加载时就存在,所以需要定义在Road 类的构造函数中作为初始化数据。

  • 最后一个用到计时器的地方是交通灯的控制系统。该计时器是每隔5s更改一次灯的当前状态,且是随着枚举类 Lamp 中的枚举元素的变化而变化。
private Lamp currentLamp;
LampController() {
//刚开始让由南向北的灯变绿;
currentLamp = Lamp.S2N;
currentLamp.turnGreen();
ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
/*计时器:每隔5秒将当前绿灯变为红灯,并让下一个方向的灯变绿*/
timer.scheduleAtFixedRate(
                new Runnable() {
                        @Override
                        public void run() {
                                currentLamp = currentLamp.turnRed();
                        }
                }, 
                10, 
                5, 
                TimeUnit.SECONDS);
}

  2、第二个设计难点就是关于Lamp 中枚举元素的定义。如果设计对于灯的设计不合理,将会导致很多不必要的麻烦。首先先前分析过,一共有12个灯,即12个枚举元素,除了其中的四盏为常绿(true标记)状态外,其余8盏灯都是两两相对应的。所以可以把这8盏灯分成两组来处理,我们只需操作其中的一组,另一组通过定义一个 opposite 字段来记录即可。对于这一组的枚举元素设定很具技巧性,我们可以把相对应灯和下一个变绿的灯当作一个参数传递到当前枚举元素中。如 S2N(N2S,S2W,false) ,但这个定义方式会报错,因为只有S2N 元素被声明了,而N2S 和S2W 对象却没有被声明,所以不能使用对象作为另一个枚举元素的参数。当把参数改为字符串类型时,就OK了S2N("N2S","S2W",false),false表示初始灯的状态为红色。至于对元素中参数的操作通过枚举的 valueOf(String str) 方法即可调用到对等的枚举元素。

        枚举元素的声明方式如下,理解并掌握这种定义方式,方便编码的实现: 

                /*每个枚举元素各表示一个方向的控制灯*/ 
                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);

        对于灯由绿变红和又红变绿的功能定义相对简单,这两种功能的体现主要是对枚举元素及其参数的操作。针对三个参数声明三个字段与其对应,然后在操作这三个字段即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值