August 04
软件实现技术课结课了,最终小组取得了第二名的成绩。
这门课,是由软微学院和微软联合创办的课程,虽然开始选这门课的时候,更多的是看重的微软的这个名字。不过学完之后看过来,的确学到了一些很有用的东西,受益匪浅。
总结一下:
- Learning by Doing
通过实践来学习,掌握技能和技巧。要努力提高自己遇到问题解决问题的能力。 - 算法设计要提到一个非常高的重视程度
尽管在开发时,架构、框架、管理……一切都很重要,但是算法绝对不可以忽视,有了足够好的算法,系统才能更加稳定更加健壮,也更加完善地运行
加强自己的算法设计能力,通过研究算法,加强自己对操作系统的认识,提高逻辑思维能力 - 做就要做最好的
在任何时候,能够被大家记住的,只有第一名。
在参与竞争时,要给自己暗示:只有两个名次,要么做第一名,要么做除了第一名以外的并列最后一名。
我们都记得神五飞船杨利伟的名字,可是又有多少人还能马上叫出的神六的两位航天员的名字呢? - 向比自己做的好的人和团队借鉴经验
正所谓踏在巨人的肩膀上前进,这样才能事倍功半 - 各自在团队中扮演好自己的角色,做出自己应有的贡献
没有PM的组织,团队是一团散沙;没有Dev的辛勤开发,系统无法运行;没有Test的认真测试,系统正确性无法保证。三足鼎立,就像三角形,缺一角,就变得不够稳定了。 - Nothing is impossible
没有什么是做不到的,只要你想,并且为之付出足够的努力
我组的PM杰哥,获得了整个课程的最佳PM奖,恭喜杰哥!没有他,小组的沟通与进度便无法保证,大家也不会如此重视。
最后,欢迎各位去我的Blog:http://www.techwork.cn/
--- Paul
July 27
软件实现技术(补)第十四次课
7月25日本周五是补上周日这门课的第十四次课,微软的工程师对我们这门课的大作业“嵩山项目”进行讲评。我们前6各小组做的都是电梯的小项目由一位老师进行讲评;后面6个小组做了3个小项目,包括电梯、自动售货机和汽车,由一位老师进行讲评。老师在讲个过程中主要是讲了每个小组代码的优点以及需要改进的地方。
我们小组的情况是:
优点:1、面向对象的意图很明显,使用多个类协同完成;
2、大量大小函数的内聚比较好;
3、采用模块化,各模块具有很强的独立性,容易维护和升级。
需要改进的地方:
1、用死循环查询(设置)电梯状态。在电梯等待或者空闲状态也会一直在扫描电梯的状态,有一定的浪费;
2、命名规则前后不一致;
3、有些函数和变量名字起的不恰当;
4、缺少必要的错误检查处理;
5、有些方法可以改为C#的属性。
以上这些就是我们小组的情况。需要改进的地方,第一,我们将电梯状态查询/设置的算法可以细化到根据某些状态需要每次都扫描,有些状态不需要扫描例如空闲和停止状态,但是可以有一个状态激活,如果这两个状态改变了就要每次都进行扫描。第二,在开发过程中两名开发人员的编码习惯有一定的不同,在编码完成后应该让开发小组负责人进行代码审核。
感谢老师在繁忙的工作中抽时间对我们代码的评审!
July 20
最近将一个项目的数据库由SQL Server 2000迁移到了2005,而在这过程中出现了一点点问题,特总结如下:
1、如何找到SQL Server 2005 的 JDBC 驱动?
http://www.microsoft.com/downloads/details.aspx?displaylang=zh-cn&FamilyID=6d483869-816a-44cb-9787-a866235efc7c
2、如果管理SQL Server 2005?
使用SQL Server Management Studio:http://www.microsoft.com/downloads/details.aspx?displaylang=zh-cn&FamilyID=c243a5ae-4bd1-4e3d-94b8-5a0f62bf7796#filelist
3、如何设置SQL Server 服务器?
(1). 打开SQL Server Configuration Manager -> MSSQLSERVER的协议 -> TCP/IP
(2). 右键单击启动TCP/IP
(3). 双击进入属性,把IP地址中的IP all中的TCP端口设置为1433
(4). 重新启动SQL Server 2005服务中的MSSQLSERVER服务器
(5). 关闭SQL Server Configuration Manager
(6). 现在就可以使用1433端口通过TCP/IP协议访问SQL Server 2005了
4、原来是SQL Server 2000的程序,为什么转移到2005中连接不了了?
(1). 加载的驱动不同了,SQL Server 2005的JDBC驱动JAR包为:sqljdbc.jar,原来的则有三个。
(2). JAR包的类名和连接字符串不同了。在SQL Server 2000 中加载驱动和URL路径的语句是
String driverName = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
String dbURL = "jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sample";
而SQL Server 2005 中加载驱动和url的语句则为
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
String dbURL = "jdbc:sqlserver://localhost:1433; DatabaseName=sample";
这里需要十分注意。
(3). 经过上述修改,便可以正确连接SQL Server 2005
今天是代码分析的最后一部分:最关键的四个函数。
从最简单的说起吧。
/// <summary>
/// 运行到某层时检查是否需要停下
/// </summary>
/// <param name="floor"></param>
/// <param name="direction"></param>
/// <returns></returns>
protected bool NeedStop(int floor, Direction direction)
{
if (_stopfloors.ContainsKey(floor))
{
_stopfloors.Remove(floor);
_task.NeedStop(floor, direction);
return true;
}
else if (_task.NeedStop(floor, direction))
{
return true;
}
else
{
return false;
}
}
NeedStop()函数判断了在某层、某方向上是否需要停止。在判断的时候,我们需要考虑外部呼梯和内部呼梯两种情况;同时,如果可以停,那么还不能忘记了把相关的层数从相关的列表中去除,也就是马上要停到该层,该层的呼梯任务完成。而在检查外部呼梯的过程中,直接调用了Task类已经写好的同名函数NeedStop(),大家也可以看我们曾经的日志来检查那个函数的写法。
/// <summary>
/// 检查是否还有同方向上更多的层需要经停
/// </summary>
/// <param name="floor"></param>
/// <param name="direction"></param>
/// <returns></returns>
protected bool ContainsMore(int floor, Direction direction)
{
switch (direction)
{
case Direction.up:
//首先检查内部呼梯
for (int i = 0; i < _stopfloors.Count; i++)
{
if (floor < _stopfloors.Keys[i])
{
return true;
}
}
//再检查外部呼梯
if (_task.ContainsMore(floor, direction))
{
return true;
}
break;
case Direction.down:
//首先检查内部呼梯
for (int i = 0; i < _stopfloors.Count; i++)
{
if (floor > _stopfloors.Keys[i])
{
return true;
}
}
//再检查外部呼梯
if (_task.ContainsMore(floor, direction))
{
return true;
}
break;
case Direction.none:
break;
}
return false;
}
ContainsMore()这个函数主要是判断电梯是否需要继续上行。例如,电梯在3层,5层有个向下呼梯。那么到了5层,电梯便可以转变方向而不用再上到六层。同样,这个函数也需要分别检查内部呼梯和外部呼梯两个列表,而在调用外部呼梯的检查过程时使用了Task类的同名函数。
下一个函数是FindDirection(),判断电梯的运行方向。这个过长会发生在stopping阶段。判断电梯下一步的运行方向如何。大家看了程序就很容易明白,因为代码注释写的很清楚了:
/// <summary>
/// 判断电梯下一步的运行方向
/// </summary>
/// <returns>电梯下一步要运动的方向</returns>
public Direction FindNextDirection()
{
//如果外部呼梯和内部呼梯均没有内容,则原地待命
if (_stopfloors.Count==0 && _task.ifEmpty())
{
return Direction.none;
}
//如果现在电梯处于上升或停止状态,那么判断本楼层以上是否还有呼梯
if (_direction == Direction.none || _direction == Direction.up)
{
//如果还有未完成上行的任务
if (ContainsMore(_floor.Current, Direction.up))
{
return Direction.up;
}
//如果还有未完成下行的任务
else if (ContainsMore(_floor.Current, Direction.down))
{
return Direction.down;
}
}
else if(_direction == Direction.down)
{
//如果还有未完成下行的任务
if (ContainsMore(_floor.Current, Direction.down))
{
return Direction.down;
}
//如果还有未完成上行的任务
else if (ContainsMore(_floor.Current, Direction.up))
{
return Direction.up;
}
}
return Direction.none;
}
最后就是最重要的RefreshStatus()函数了。它为电梯状态改变,提供了转换及其发生的相关条件判断。
/// <summary>
/// 刷新运行状态
/// </summary>
protected void RefreshStatus()
{
while (_runinngPermit)
{
int ticks = Environment.TickCount;
int activetime = ticks - _lastactive;
switch (_status)
{
case Status.Out_of_Services:
//如果电梯处在不可用状态,那么不予请求,直到电梯开启使用
break;
case Status.Maintenance:
//如果电梯处在维护状态,那么不予请求
break;
case Status.Idle:
//电梯处在空闲状态,此时如果有外部呼梯存在,或是内部请求存在,则决定前往的方向,将状态改变为上或下
_direction = FindNextDirection();
switch (_direction)
{
case Direction.up:
_status = Status.Up;
break;
case Direction.down:
_status = Status.Down;
break;
}
break;
case Status.Up:
//电梯在上升的过程中,每5秒上升一层。在本层判断下一层是否是经停层。如果是,则将状态转化为停止。
if (activetime < 2000)
{
break;
}
else
{
if (NeedStop(_floor.Current, _direction))
{
_status = Status.Stopping;
}
else
{
//如果到达顶层则向下
if (_floor.Current == _floor.getTopFloor())
{
_status = Status.Down;
_direction = Direction.down;
}
else
{
_floor.Up();
}
}
_lastactive = ticks;
}
break;
case Status.Down:
//电梯在下降的过程中,每2秒下降一层。在本层判断下一层是否是经停层。如果是,则将状态转化为开门。
if (activetime < 2000)
{
break;
}
else
{
if (NeedStop(_floor.Current, _direction))
{
_status = Status.Stopping;
}
else
{
//如果到达底层则向上
if (_floor.Current == _floor.getBottomFloor())
{
_status = Status.Up;
_direction = Direction.up;
}
else
{
_floor.Down();
}
}
_lastactive = ticks;
}
break;
case Status.Stopping:
//电梯停止的过程中。这里判断下一步是继续上升还是下降
if (activetime >= 2000)
{
_status = Status.Opening;
_lastactive = ticks;
}
_direction = FindNextDirection();
break;
case Status.Opening:
//电梯门打开,打开时间长3秒钟
if (activetime >= 3000)
{
_status = Status.Waiting;
_lastactive = ticks;
}
break;
case Status.Waiting:
//开门后,电梯停顿2秒,关门
if (activetime >= 2000)
{
_status = Status.Closing;
_lastactive = ticks;
}
break;
case Status.Closing:
//电梯门关闭,关闭时间长3秒钟
if (activetime >= 3000)
{
_status = Status.Idle;
_lastactive = ticks;
}
break;
}
StringBuilder status = new StringBuilder();
status.AppendLine("Current Status:");
status.AppendLine("Floor:" + _floor.Current + " Direction:" + _direction + " Status:" + _status);
status.AppendLine();
status.AppendLine("Outside Calls:");
status.AppendLine(_task.ListAll());
status.AppendLine("Inside Calls:");
status.AppendLine(ListAllStopFloors());
GetAllStatus(status.ToString());
Console.WriteLine(status.ToString());
Thread.Sleep(500);
}
}
这段代码主要就是根据目前status状态,以及上一次系统活动的时间,来判断下一步电梯做什么,状态如何。有了上面很多关键函数的支撑,这里其实已经简化了很多,更多的都是周边的设置和输入输出工作了了。在正式运行时,我们激活了一个线程,每隔500毫秒执行一次本程序。最后,通过委托的挂接,将结果传送到高层模块中,以最终完成输出工作。
整个电梯的设计工作及代码分析到此结束了。在这一段日子里,我们通过这个程序,根据自己的需求和设计进行编码和实现,完成了电梯运行的再现。
最后一次的entry,将会展示我们电梯的测试类,并展示电梯的运行效果。
July 19
今天来主要分析电梯程序设计中最关键的类:ElevatorMain
首先贴出类的声明和它的字段列表:
/// <summary>
/// 电梯主类
/// </summary>
public class ElevatorMain
{
/// <summary>
/// 电梯外部呼梯任务
/// </summary>
private Task _task;
/// <summary>
/// 电梯内部经停任务
/// </summary>
private SortedList<int, int> _stopfloors;
/// <summary>
/// 电梯运行状态
/// </summary>
private Status _status;
/// <summary>
/// 电梯所有可以经停的楼层
/// 以及现在所处的楼层
/// </summary>
private Floor _floor;
/// <summary>
/// 电梯运行的方向
/// </summary>
private Direction _direction;
/// <summary>
/// 最后一次系统活动时间
/// </summary>
private int _lastactive;
//控制线程
private Thread _running;
//线程运行许可
private bool _runinngPermit = true;
可以看出,我们的电梯程序中,主要用了如下字段:
- _task: 用来存储外部呼梯任务的Task对象
- _stopfloors: 保存内部电梯任务的对象,用SortedList<int,int>作为其载体
- _status: 存储电梯的各个运行状态
- _floor: 保存电梯想对应的层数信息,如当前层、层数列表等等
- _direction: 保存电梯运行的方向
- _lastactive: 存储上次电梯活动的时刻,用来计算电梯的运行时长来判断下一个任务的执行
有了这些相关类型数据的存储,就可以保证程序的顺利运行了。
/// <summary>
/// 电梯类构造函数
/// </summary>
/// <param name="floors">可以经停的楼层信息</param>
public ElevatorMain(Floor floor)
{
_task = new Task();
_stopfloors = new SortedList<int, int>();
_floor = floor;
_status = Status.Out_of_Services;
_direction = Direction.none;
_lastactive = Environment.TickCount;
_running = new Thread(new ThreadStart(this.RefreshStatus));
_running.Name = "Running";
_running.Start();
}
上述函数是电梯运行的构造函数。将一些基本的数据初始化,还有就是新建一个线程,来运行RefreshStatus检测系统状态。这里使用了Polling轮询的方法,电梯实际运行中,应该会有一些触发的过程来处理。
/// <summary>
/// 开启电梯到运行状态
/// </summary>
public void Startup()
{
if (_status == Status.Out_of_Services || _status == Status.Maintenance)
{
_status = Status.Idle;
}
}
/// <summary>
/// 关闭电梯
/// </summary>
public void Closedown()
{
if (_status == Status.Idle || _status == Status.Maintenance)
{
_status = Status.Out_of_Services;
}
}
/// <summary>
/// 维修电梯
/// </summary>
public void Repair()
{
if (_status == Status.Out_of_Services || _status == Status.Idle)
{
_status = Status.Maintenance;
}
}
/// <summary>
/// 外部呼梯
/// </summary>
/// <param name="floor">楼层</param>
/// <param name="direction">方向</param>
public void Call(int floor, Direction direction)
{
//如果电梯尚未运行则退出
if (_status == Status.Out_of_Services || _status == Status.Maintenance)
{
return;
}
//如果没有这一层则退出
if (!_floor.AllFloors.Contains(floor))
{
return;
}
//如果顶层按上行则退出
if (floor == _floor.AllFloors[_floor.AllFloors.Count - 1] && direction == Direction.up)
{
return;
}
//如果底层按下行则退出
if (floor == _floor.AllFloors[0] && direction == Direction.down)
{
return;
}
//增加任务
_task.AddTask(floor, direction);
}
上述几个函数主要完成了电梯的开启、关闭、维修、外部呼梯过程。前几个较为简单,主要说下外部呼梯。其思想为:只有在电梯处于正常状态(非关闭、维护状态)才可以呼梯;不能在没有注册到电梯层数中的某层呼梯(比如电梯没有第4层,那么在第4层就不能呼梯);最高层不能向上呼梯;最底层不能向下呼梯。
/// <summary>
/// 内部按键
/// </summary>
/// <param name="key">楼层号</param>
public void Click(int key)
{
//如果电梯未运行则退出
if (_status == Status.Out_of_Services || _status == Status.Maintenance)
{
return;
}
if (!_stopfloors.ContainsKey(key))
{
_stopfloors.Add(key, key);
}
}
/// <summary>
/// 内部按键
/// </summary>
/// <param name="special_key">特殊按键</param>
public void Click(SpecialKeys special_key)
{
if (_status == Status.Out_of_Services || _status == Status.Maintenance)
{
return;
}
switch (special_key)
{
case SpecialKeys.Alarm:
_status = Status.Out_of_Services;
break;
case SpecialKeys.Call_Outside:
// no use
break;
case SpecialKeys.Open_Door:
if (_status == Status.Waiting)
{
//等待状态下按下开门
_lastactive = Environment.TickCount;
}
else if (_status == Status.Closing)
{
//关门状态按下开门
_lastactive = Environment.TickCount;
_status = Status.Opening;
}
break;
case SpecialKeys.Close_Door:
if (_status == Status.Waiting)
{
//等待状态按下关门
_lastactive = Environment.TickCount;
}
/*
if (_status == Status.Opening)
{
//开门状态按下关门
_lastactive = DateTime.Now.Ticks;
_status = Status.Closing;
}*/
break;
}
}
这两个函数是内部呼梯出发的函数。分为两个:一个是按普通的楼层按键,另一个则是按特殊按键。分别处理,重载函数,则可以显示程序的多态性。
/// <summary>
/// 取得到现在的运行状态
/// </summary>
/// <returns>当前运行状态</returns>
public Status GetCuurentStatus()
{
return _status;
}
/// <summary>
/// 取得到当前的楼层号
/// </summary>
/// <returns>当前楼层号</returns>
public int GetCurrentFloor()
{
return _floor.Current;
}
/// <summary>
/// 取得到电梯运行方向
/// </summary>
/// <returns>电梯当前运行方向</returns>
public Direction GetDirection()
{
return _direction;
}
这三个函数即为取得到电梯当前运行状态的函数。通过它们,就可以准确判断电梯现在的状态了。
为了外部测试程序的调用,我又写了如下几个相关函数:
/// <summary>
/// 列出所有内部经停的层
/// </summary>
/// <returns></returns>
protected string ListAllStopFloors()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < _stopfloors.Count; i++)
{
sb.AppendLine(_stopfloors.Keys[i].ToString() + "th floor");
}
return sb.ToString();
}
/// <summary>
/// 设置委托以便参数检查
/// </summary>
/// <param name="status"></param>
public delegate void GetAllStatusDelegate(string status);
/// <summary>
/// 设置事件
/// </summary>
public event GetAllStatusDelegate GetAllStatus;
public void StopRunning()
{
_runinngPermit = false;
}
通过委托和事件的使用,就可以使外部的测试程序挂接到本类上,以取得到相关的输出更新。
到目前为止,程序已经介绍了大半部分,还剩下四个最为关键的函数。敬请期待。