关于本次结对编程项目的附加讨论。
结对项目 http://www.cnblogs.com/hks1994/p/4034028.html
一. 关于接口设计
本次项目中专门建了一个project,其中的commons.cs文件中定义了所有的接口以及结构体,这是非常好的抽象层次构建,让我非常欣赏。不过我在对该项目进行增量开发的时候,由于算法的设计,被迫在接口设计中添加了一些成员变量和方法,以下举几个例子:
1.在每个电梯类内部维护一个数组,数组元素为int,表示电梯楼层内请求
2.在Scheduler类中维护一个数组,数组元素为结构体,结构体内有两个int,分别表示向上的楼层请求以及向下的楼层请求。维护全局的楼层请求。
3.如果需要考虑换乘,则IRequest请求类接口中应该增加一个域表示该请求由哪位用户发出,以便换乘的时候重新发出中转楼层的请求。
二. 关于UI设计
之前在Eclipse中曾经做过建议的java图形界面开发学习,也尝试过在vs2010中用c#进行图形界面开发一个计算器,因此对图形界面开发有浅显的了解。
初始想法是做一个比较纯粹的图形界面,即电梯是长方形,人是火柴人,但是后来考虑到有点太复杂了(其实仔细想想说不定也可以做出来)就有点吓怕了,怂了之后,马上决定利用表格表示电梯和用户的运动情况,本质就是把源程序中的大部分Console.WriteLine输出重定向到对应的表格中,说起来是很简单的,不过我对实时更新还是觉得没底,因为没做过。
开发初期因为同一个solution中的各个project之间的reference关系而烦恼不已,为此还专门建了两个与UI有关的project,原因一是UI需要调用World.Program的Main方法并传递参数,需要在UI的project中引用World;而要想实时更新电梯状态以及用户状态,则最简单的方式之一就是在每个writeline方法调用时改为把信息输出到表格汇中即可(改变表格中对应项的文本内容即可),则World名空间需要引用UI的project,这样会形成circular reference而导致vs2012报错。即使采用MVC模式,在V(UI的project)和M(主程序)之间加一个C(Controller类),依然会形成circular reference。查资料未果,实验很久之后我决定建两个UI project,一个负责World,一个负责Controller,也就是把UI的功能拆分,这样不会形成circular reference。
这样UI的逻辑就很简单了。首先ViewDriver名空间内弹出初始界面,让用户选择xml文件,然后通过按钮触发同一个名空间内的第二个窗口,该窗口负责电梯监视窗口以及用户监视窗口的开启以及主程序的运行。对于两个监视窗口,每个监视窗口内需要显示当前tick以及一个表格显示所有电梯/乘客的状态,表格我选择了ListBox,因为以前也没做过图形界面的表格,因此不太熟就选了一个增减方便,有滚动条而且可以改项的文本域的box,但是无奈的是ListBox好像不能自由根据窗口大小变化而相应更改大小。
最担心的实时更新问题,就轻易地被解决了,就是把主程序里的输出语句都改为调用controller里的方法,把信息显示在表格中。
信息输出正常之后,开始优化按钮操作,以至于现在按钮无限按也不会触发异常而崩溃。
一大遗憾就是在主程序运行时,其他的所有窗口都不能动,滚动条也不能拉,所以只能一开始就拉好滚动条然后看想看的顾客。并不是没有考虑过多线程,而是窗口太多了,线程太多,尝试了也无果,时间也不太够,就没抠那么细。
以下是部分UI源程序 以及UI运行界面截图:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using User_Interface; 11 using Controller; 12 using System.Threading; 13 using Elevator; 14 using Passenger; 15 using commons; 16 17 namespace ViewDriver 18 { 19 public partial class Form1 : Form 20 { 21 public Form2 fm2 { get; set; } 22 public Form3 fm3 { get; set; } 23 public Form4 fm4 { get; set; } 24 public static string[] args { get; set; } 25 //static List<SenElevator> AllElevators; 26 //static List<SigmaPassenger> AllPassengers; 27 28 29 public Form1() 30 { 31 args = new string[2]; 32 InitializeComponent(); 33 } 34 35 private void Form1_Load(object sender, EventArgs e) 36 { 37 38 } 39 40 private void label1_Click(object sender, EventArgs e) 41 { 42 43 } 44 45 private void panel1_Paint(object sender, PaintEventArgs e) 46 { 47 48 } 49 50 private void label2_Click(object sender, EventArgs e) 51 { 52 53 } 54 55 private void button1_Click(object sender, EventArgs e) 56 { 57 this.openFileDialog1.ShowDialog(); 58 this.textBox1.Text = this.openFileDialog1.FileName; 59 } 60 61 private void button2_Click(object sender, EventArgs e) 62 { 63 this.openFileDialog2.ShowDialog(); 64 this.textBox2.Text = this.openFileDialog2.FileName; 65 } 66 67 private void button3_Click(object sender, EventArgs e) 68 { 69 args[0]=this.textBox1.Text; 70 args[1]=this.textBox2.Text; 71 //World.Program.Loader(args[0],args[1],ref AllElevators,ref AllPassengers); 72 //args = {this.textBox1.Text,this.textBox2.Text}; 73 if (fm2 != null && fm2.Visible) 74 { 75 return; 76 } 77 fm2 = new Form2(); 78 fm2.SetLists(args[0],args[1]); 79 //fm3 = new Form3(); 80 //fm4 = new Form4(); 81 //fm2.Initialize(fm3, fm4); 82 //Controller.Controller.StartUp(fm3, fm4); 83 fm2.Show(); 84 //Controller.Controller.InitializeHash(); 85 //foreach (SenElevator elev in AllElevators) 86 //{ 87 // Controller.Controller.InitializeView("elevators", "elevator " + elev.ID + " is at floor " + elev.CurrentStatus.CurrentFloor + " in direction no"); 88 //} 89 // int order = 0; 90 // foreach (SigmaPassenger p in AllPassengers) 91 // { 92 // Controller.Controller.InitializeView("passengers", "passenger " + p.Name + " has not been at this waiting hall"); 93 // Controller.Controller.PassengerHash.Add(p.Name, order++); 94 //} 95 } 96 97 private void button4_Click(object sender, EventArgs e) 98 { 99 this.Close(); 100 } 101 } 102 }
初始界面,待选择xml文件路径
选择路径
两个xml文件均选择完毕
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using User_Interface; 11 using Elevator; 12 using Passenger; 13 14 namespace ViewDriver 15 { 16 public partial class Form2 : Form 17 { 18 19 private Form3 fm3; 20 private Form4 fm4; 21 static List<SenElevator> AllElevators; 22 static List<SigmaPassenger> AllPassengers; 23 24 public Form2() 25 { 26 InitializeComponent(); 27 } 28 29 public void SetLists(string elevator,string passenger) 30 { 31 World.Program.Loader(elevator, passenger, ref AllElevators, ref AllPassengers); 32 fm3 = new Form3(); 33 fm4 = new Form4(); 34 Controller.Controller.StartUp(fm3, fm4); 35 Controller.Controller.InitializeHash(); 36 int order = 0; 37 foreach (SigmaPassenger p in AllPassengers) 38 { 39 //Controller.Controller.InitializeView("passengers", "passenger " + p.Name + " has not been at this waiting hall"); 40 Controller.Controller.PassengerHash.Add(p.Name, order++); 41 } 42 } 43 44 public void Initialize(Form3 fm3, Form4 fm4) 45 { 46 this.fm3 = fm3; 47 this.fm4 = fm4; 48 } 49 50 private void Form2_Load(object sender, EventArgs e) 51 { 52 53 } 54 55 private void button3_Click(object sender, EventArgs e) 56 { 57 this.Close(); 58 } 59 60 private void button1_Click(object sender, EventArgs e) 61 { 62 if (fm3.Visible) 63 { 64 return; 65 } 66 if (fm3.IsDisposed) 67 { 68 fm3 = new Form3(); 69 Controller.Controller.win3 = fm3; 70 } 71 foreach (SenElevator elev in AllElevators) 72 { 73 Controller.Controller.InitializeView("elevators", "elevator " + elev.ID + " is at floor " + elev.CurrentStatus.CurrentFloor + " in direction no"); 74 } 75 fm3.Show(); 76 } 77 78 private void button2_Click(object sender, EventArgs e) 79 { 80 if (fm4.Visible) 81 { 82 return; 83 } 84 if (fm4.IsDisposed) 85 { 86 fm4 = new Form4(); 87 Controller.Controller.win4 = fm4; 88 } 89 //int order = 0; 90 foreach (SigmaPassenger p in AllPassengers) 91 { 92 Controller.Controller.InitializeView("passengers", "passenger " + p.Name + " has not been at this waiting hall"); 93 //Controller.Controller.PassengerHash.Add(p.Name, order++); 94 } 95 fm4.Show(); 96 } 97 98 private void button4_Click(object sender, EventArgs e) 99 { 100 if (Controller.Controller.win5.IsDisposed) 101 { 102 Controller.Controller.StartUp(fm3, fm4); 103 } 104 if (!fm3.Visible || !fm4.Visible) 105 { 106 return; 107 } 108 World.Program.Main(Form1.args); 109 } 110 } 111 }
点击begin,开启第二个窗口
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 11 namespace User_Interface 12 { 13 public partial class Form3 : Form 14 { 15 public Form3() 16 { 17 InitializeComponent(); 18 } 19 20 public void AddItemtoBox(string info) 21 { 22 this.listBox1.Items.Add(info); 23 } 24 25 public void ChangeBoxItem(int id, string str) 26 { 27 this.listBox1.Items[id] = str; 28 } 29 30 private void button1_Click(object sender, EventArgs e) 31 { 32 this.Close(); 33 } 34 35 public void UpdateTick(string str) 36 { 37 this.label1.Text = str; 38 } 39 40 private void Form3_Load(object sender, EventArgs e) 41 { 42 43 } 44 45 } 46 }
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 11 namespace User_Interface 12 { 13 public partial class Form4 : Form 14 { 15 public Form4() 16 { 17 InitializeComponent(); 18 } 19 20 public void AddItemtoBox(string info) 21 { 22 //win4.AddItemtoBox(info); 23 this.listBox1.Items.Add(info); 24 } 25 26 public void ChangeBoxItem(int num,string info) 27 { 28 this.listBox1.Items[num] = info; 29 } 30 31 private void button1_Click(object sender, EventArgs e) 32 { 33 this.Close(); 34 } 35 36 public void UpdateTick(string str) 37 { 38 this.label1.Text = str; 39 } 40 } 41 }
在启动主程序之前,需要先启动elevators和passengers的监视器(watching),使两个窗口都显示,否则点击launch不会有任何反应。两个监视窗口在显示之前就已经分别载入了电梯和乘客的信息
点击launch,主程序开始运行,两个watching界面实时更新
主程序运行,监视器(watching)界面实时更新
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 11 namespace User_Interface 12 { 13 public partial class Form5 : Form 14 { 15 public Form5() 16 { 17 InitializeComponent(); 18 } 19 20 private void button1_Click(object sender, EventArgs e) 21 { 22 this.Close(); 23 } 24 25 public void AddItemtoBox(string str) 26 { 27 this.listBox1.Items.Add(str); 28 } 29 } 30 }
主程序运行完毕,弹出summary窗口进行时间总结,滚动条拉到最下面可以看到平均消耗时间
在watching passengers窗口中选择另一个项(不选中项则区域默认为所有项中最上面21个项),也可以说是换一个区域(换另外21个项)进行监视,重新开始监视
继续监视
三. 关于MVC 和 MVVM 设计模式
MVC和MVVM都是软件设计模式。MVC代表Model-Controller-View,MVVM代表Model-ViewModel-View。鉴于两者结构和设计目的较为相似,MVVM由MVP发展而来,MVP又由MVC发展而来,因此这里着重探讨MVC设计模式。
MVC实际上非常简单,就是把软件分为三个部分:Model View Controller。以B/S架构为例来说明。B/S架构中的数据库相当于Model,包括数据以及对数据的操作,也就是数据库原理课上讲的数据模型;网页前段相当于View,充当User Interface处理用户交互请求;而数据库原理课上讲的外模式相当于Controller负责控制前段和后端的数据传输请求处理。使用MVC架构可以将一整个软件分成几个部分,而这几个部分是相对独立的,有利于程序员分工开发,耦合之时只需配合接口即可。一个部分的具体实现的更改可能不需要其他部分的更改,而仍可以保持整体功能完整。或者更直接地说,就是把View和Model分开,Controller也只是控制协调这两个部分而已,使得View和Model都具有一定的独立性。
在设计UI的过程中,我发现我的程序有着非常明显的MVC风格。我的UI写了两个project,都是View的部分;Controller专门写了一个类;剩下的主程序就是Model的部分。下面附上我的Controller类代码进行说明:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using User_Interface; 7 using System.Collections; 8 9 namespace Controller 10 { 11 public class Controller 12 { 13 //Form1 win1; 14 //static Form2 win2; 15 public static Form3 win3 { get; set; } 16 public static Form4 win4 { get; set; } 17 public static Form5 win5 { get; set; } 18 public static Hashtable PassengerHash { get; set; } 19 20 /*public static bool StartWatching() 21 { 22 if (!win3.IsDisposed || !win4.IsDisposed) 23 { 24 return false; 25 } 26 return true; 27 }*/ 28 29 public static void InitializeHash() 30 { 31 PassengerHash = new Hashtable(); 32 } 33 34 public static void UpdatePassengerStatus(string name, string info) 35 { 36 int num; 37 num = (int)PassengerHash[name]; 38 win4.ChangeBoxItem(num, info); 39 //win4.listBox1.Items[num] = info; 40 } 41 42 public static void UpdateElevatorStatus(int id, string str) 43 { 44 win3.ChangeBoxItem(id, str); 45 //win3.listBox1.Items[id] = str; 46 } 47 48 public static void Summarize(string str) 49 { 50 win5.Show(); 51 win5.AddItemtoBox(str); 52 //win5.listBox1.Items.Add(str); 53 } 54 55 public static void StartUp(Form3 fm3, Form4 fm4) 56 { 57 //win2 = fm2; 58 win3 = fm3; 59 win4 = fm4; 60 win5 = new Form5(); 61 } 62 63 public static void UpdateTicks(string str) 64 { 65 win3.UpdateTick(str); 66 win4.UpdateTick(str); 67 } 68 69 public static void InitializeView(string type, string info) 70 { 71 if(type.Equals("elevators")) 72 { 73 win3.AddItemtoBox(info); 74 //win3.listBox1.Items.Add(info); 75 } 76 else if (type.Equals("passengers")) 77 { 78 win4.AddItemtoBox(info); 79 //win4.listBox1.Items.Add(info); 80 } 81 } 82 } 83 }
在主程序(Model)中,定义有电梯类、乘客类等数据类,这些类的数据是UI需要的,而Scheduler则对这些数据类进行了一定的调度,Loader负责把最原始的数据加载进来,经过处理之后数据变为一个个电梯对象和乘客对象。因此主程序可以看做是Model。
至于两个与UI有关的project。一个是ViewDriver,其中的两个类各负责一个窗口,另一个project User-Interface其中的三个类各自负责一个窗口,因此这两个project可以看做View。
Controller类中,为了方便,设置了一系列静态方法便于主程序和UI中调用。下面对Controller类中各个方法进行介绍:
UpdatePassengerStatus(string name, string info):当主程序中顾客状态改变时(上车下车换乘),主程序调用该方法,该方法调用win4.ChangeBoxItem(num, info);改变顾客监视窗口中对应项的值
UpdateElevatorStatus(int id, string str):当主程序中电梯状态改变时,主程序调用该方法,该方法调用win3.ChangeBoxItem(id, str);更改电梯监视窗口中对应项的文本
Summarize(string str):主程序中开始输出每个用户的耗时信息时,调用该方法:将一条用户耗时信息加入到summary所在控件的ListBox之中,显示在窗口中
UpdateTicks(string str):主程序中增加tick时,调用相应对象(窗口)的UpdateTick更新窗口中显示时间的label的文本
InitializeView(string type, string info):将两个活动监视器的内容在它们显示之前初始化
从上面的可以看出,Controller类中的方法被主程序调用,然后Controller再根据主程序的请求调用UI的函数,实现了数据的实时更新。而用户通过点击不同的按钮,打开不同的窗口,浏览不同的数据。
我在编写图形代码的时候,发现有一个图形的类放错了工程,于是我将它移走了,但是主程序中的那些请求controlller的代码不需要更改,只需要改UI和Controller中的代码就行了,这是因为controller向主程序提供了一系列实时更新的接口,而接口的内部实现不需要主程序管,所以主程序只要遵循接口就可以进行实时更新,将View和Model分开,各自具有一定的独立性,是典型的MVC架构。及时我的数据模型改变了,但是只要继续遵循controller的接口,View仍然能够显示数据,而不需关心数据的具体模型是怎样的。实现了View和Model的分离。