《面向对象程序设计》课程设计任务及指导书
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《面向对象程序设计》
课程设计
任务与指导书
绍兴文理学院计算机系
《面向对象程序设计》课程设计指导书
设计任务与目的
《面向对象程序设计》是一门实践性较强的软件基础课程,在理论课的基础上,通过《面向对象程序设计》的课程设计,使学生学会在.net平台开发环境下编写综合型、设计型的程序;巩固学生的的基础知识;培养学生采用面向对象的方法设计并调试较大型程序的动手能力,并更好地理解和消化课本所学的知识,为今后的实际应用打下良好的基础,课程设计侧重培养一下几方面的能力:
(1)培养对问题中分析以及归纳和抽象到数据结构的能力;
⑵培养选择和应用数据存储结构的能力;
⑶培养数据组织的能力和抽象数据类型设计的能力;
⑷培养算法设计、程序设计和调试的能力;
⑸培养综合知识、方法、技能的应用能力;
⑹培养编制较综合性程序的能力。
设计内容与基本要求
课程设计的题目应以综合性的题目为主,选择与实际应用结合较紧密的较综合性的题目,难度应大于课程实验的题目。
本次设计的主要内容是
(1)开发一个简单的教务管理系统,基本功能如下:
通过提供控制台的图形界面,允许用户以3种不同的身份登陆教务系统,分别进行相关的操作:
(1)系统管理员:管理现有课程、学期课程、教师及学生信息等
(2)教师:管理本人的学期课程,查询选修所任课程的学生列表,并给学生打分等(3)学生:查询学期课程情况,选课/退课,查询已修课程成绩等
要求学生用面向对象的思想对系统进行分析,设计所需类并实现,调试通过,基本数据信息均存放在文件中。
(2)使用Windows Form开发简单的游戏,实现基本的连连看功能(也可以是类似的别的小游戏),要求采用面向对象思想,实现算法逻辑与界面分离.
时间及指导教师安排
备注:
(1)指导教师:戴振中
(2)上机地点:理工楼303(理论课地点另定)
(3)周二(2-9周)下午1:30~4:30
考核
课程设计的成绩分为:优秀、良好、中等、及格,不及格五个等级,不及格者不得取得相应学分。
总成绩=平时成绩*30%+答辩*40%+设计报告*30%
课题一.简单教学管理系统
一、问题描述
作为一个实用的教务系统,必须能管理教学过程中的各个环节(课程设置、选课和成绩登录等),并且协调各方面的人员(系统管理员、教师、学生等),本次设计以此为背景,实现一个简单的信息管理系统来模拟教务管理系统。
二、基本要求
提供控制台的图形界面,允许用户以3种不同的身份登陆教务系统,使用菜单选择所需功能,进行相关的操作:
(1)系统管理员:管理现有课程、学期课程、教师及学生信息,并对这些信息进行相应的查询、添加、删除等操作
(2)教师:管理本人的学期课程,查询选修所任课程的学生列表,并给学生打分等
(3)学生:查询学期课程和选课情况,选择或删除选修课程,查询已修课程成绩等
要求学生用面向对象的思想对系统进行分析,设计所需类并实现,调试通过,基本数据信息存放到文件中。
三、测试数据
系统可采用如下的图形界面:
1.用户登录初始界面
2.登录后(以系统管理员为例)
3.选择相应功能(以教师管理为例)
其余功能类似,学生可根据自己的理解增加其他的相关功能,考核时可酌情加分:如
(1)课程信息中还可增加前导课程的信息,学生选择某门课时,必须保证前导
课已修;否则给出相应提示性信息
(2)开课时受班级人数限制,超过指定人数不能选课;
(3)某个学生已选的课程上课的时间不能冲突;
(4)学生每学期所选课程学分数不能超过30学分……
四、实现提示
1、设计思路
采用面向对象程序设计思想开发比较大型的软件,一般需要在两个层面上进行抽象:(一)将某类实体的“数据+行为”封装抽象,作为独立的整体与外部进行交互。
在该系统中,比较明显存在的抽象实体主要是如下五类:
(1)系统管理员:管理教师、学生、课程(学期课程)信息,主要是新记录的增加、现有记录的删除、修改和各种查询。
作为管理员不应该涉及其他角色的具体工作,比如某门课程成绩的登陆工作等;(如何进行权限管理?)
(2)教师:负责教师本人的日常工作,如个人信息(帐户、姓名、密码等)的维护,开展学
期课程教学(在本系统中主要体现在将某课程与教师绑定),所授学科成绩的登陆等等
(3)学生:进行学生相关信息的维护,如个人信息(帐户、姓名、密码等),课程信息以及成绩信息(包括已选课程、在选课程---已选但未取得成绩的课程)等
思考:上述三个类之间具有一些共同的数据成员和操作,如基本信息(帐户、姓名、密码)及基本操作(记录的增加、删除、修改、查询)等,怎样用OOP中的继承和多态等特性来提高编程效率并降低编程复杂度?
(4)课程:这个实体比较简单,只需要保持课程号、课程名及学分信息即可,该类跟其他实体的交互主要体现在它的相关类“学期课程”上
(5)学期课程:即本学期某个教师开设某门课程,它可由课程派生而来,与某个教师绑定。
学生选课/退课以及某门课程的成绩具体应着眼到学期课程上,教师可主动增/减学期课程,系统管理员则从整体管理。
注意:除了以上比较明显的各个实体之外,应该还有各实体类相应的“容器”类,来存放该类的所有实例,以便在运行期进行添加、查找、删除、更新及其他相关操作
(二)在系统实现时,数据访问层、中间层和用户接口层也必须有比较鲜明的分割,以进一步去除类间的耦合性。
在本系统中,这三层分别负责如下功能:
(1)用户接口层(UI):负责显示各层菜单及子功能包括菜单间的切换;接收用户输入,将用户输入转换成对下层(中间层)的调用;接受下层的执行结果回显给用户,也就是说与控制台直接交互的代码均在该层的类中实现
(2)业务逻辑层(BLL):负责对用户接口的各种调用作出响应,将计算结果返回给上层,在需要时调用下层(数据访问层)来读取各实体的数据
(3)数据访问层(DAL):负责真正与各类数据打交道,例如,从文件中读取管理员、教师、学生以及课程的相关信息,供上层调用;在实体执行某些修改时,将新的信息保存到文件中去。
在系统设计时,应该将各层之间的功能分割用不同类将其体现出来,尽量使各层的类各司其职而不要越界,可将各层的相关类放于不同的项目中,方便更好的代码管理和复用
2.数据结构及算法
(1)由于采用OOP思想,数据结构都已包含在各个类的定义中,主要就是实现上文所述的各个类
(2)对于与各实体类对应的“容器类”,主要是在运行期维护一组实例信息,可直接采用.net
提供的容器类List<T>来存放
(3)由于本系统未采用数据库,故所有的信息均需从文件中读写,若直接文件读写不仅实现的功能比较简单,且需要进行大量的数据处理工作,在本系统实现中可考虑采用“序列化”技术。
所谓的“序列化”是将对象状态转换为可保持或传输的格式的过程。
与序列化相对的是反序列化,它将流转换为对象。
这两个过程结合起来,就使得数据能够被轻松地存储和传输。
(祥见附录2序列化/反序列化的相关知识)
附录:相关知识
一、C# List<T>用法
所属命名空间:System.Collections.Generic
using System.Collections.Generic;
类继承关系:public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList,ICollection, IEnumerable
List<T>类是ArrayList 类的泛型等效类。
该类使用大小可按需动态增加的数组实现IList<T> 泛型接口。
泛型的好处:它为使用c#语言编写面向对象程序增加了极大的效力和灵活性。
不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高。
1、 List 的基础、常用方法:
声明:
1、List<T> mList = new List<T>();
T为列表中元素类型,现在以string类型作为例子
E.g.:List<string> mList = new List<string>();
2、List<T> testList =new List<T> (IEnumerable<T> collection);
以一个集合作为参数创建List
E.g.:
string[] temArr = { "Ha", "Hunter", "Tom", "Lily", "Jay", "Jim", "Kuku", "Locu" };
List<string> testList = new List<string>(temArr);
添加元素:
1、List. Add(T item) 添加一个元素
E.g.:mList.Add("John");
2、List. AddRange(IEnumerable<T> collection) 添加一组元素
E.g.:
string[] temArr = { "Ha","Hunter", "Tom", "Lily", "Jay", "Jim", "Kuku", "Locu" };
3、Insert(int index, T item); 在index位置添加一个元素
E.g.:mList.Insert(1, "Hei");
遍历List中元素:
foreach (T element in mList) T的类型与mList声明时一样
{
Console.WriteLine(element);
}
E.g.:
foreach (string s in mList)
{
Console.WriteLine(s);
}
删除元素:
1、List. Remove(T item) 删除一个值
E.g.:mList.Remove("Hunter");
2、List. RemoveAt(int index); 删除下标为index的元素
E.g.:mList.RemoveAt(0);
3、List. RemoveRange(int index, int count);
从下标index开始,删除count个元素
E.g.:mList.RemoveRange(3, 2);
注:删除某元素后,其后面的元素下标自动跟进
判断某个元素是否在该List中:
List. Contains(T item) 返回true或false,很实用
E.g.:
if (mList.Contains("Hunter"))
{
Console.WriteLine("There is Hunter in the list");
}
else
{
mList.Add("Hunter");
Console.WriteLine("Add Hunter successfully.");
}
给List里面元素排序:
List. Sort () 默认是元素第一个字母按升序
E.g.:mList.Sort();
给List里面元素顺序反转:
List. Reverse () 可以与List. Sort ()配合使用,达到想要的效果
E.g.:mList.Sort();
List清空:List. Clear ()
E.g.:mList.Clear();
获得List中元素数目:
List. Count () 返回int值
E.g.:
int count = mList.Count();
Console.WriteLine("The num of elements in the list: " +count);
或者List.Count;
2、 List 的进阶、强大方法:
举例用的List:
string[] temArr = { Ha","Hunter", "Tom", "Lily", "Jay", "Jim", "Kuku", " "Locu" };
mList.AddRange(temArr);
List.Find 方法:搜索与指定谓词所定义的条件相匹配的元素,并返回整个List 中的第一个匹配元素。
public T Find(Predicate<T> match);
Predicate是对方法的委托,如果传递给它的对象与委托中定义的条件匹配,则该方法返回true。
当前List 的元素被逐个传递给Predicate委托,并在List 中向前移动,从第一个元素开始,到最后一个元素结束。
当找到匹配项时处理即停止。
Predicate 可以委托给一个函数或者一个拉姆达表达式
(1)委托给拉姆达表达式:
E.g.:
string listFind = mList.Find(name => //name是变量,代表的是mList
{ //中元素,自己设定
if (name.Length > 3)
{
return true;
}
return false;
});
Console.WriteLine(listFind); //输出是Hunter
(2)委托给一个函数:
E.g.:
string listFind1 = mList.Find(ListFind); //委托给ListFind函数
Console.WriteLine(listFind); //输出是Hunter
ListFind函数定义如下:
public bool ListFind(string name)
{
if (name.Length > 3)
{
return true;
}
return false;
}
这两种方法的结果是一样的。
List.FindLast 方法:搜索与指定谓词所定义的条件相匹配的元素,并返回整个List 中的最后一个匹配元素。
public T FindLast(Predicate<T> match);
用法与List.Find相同。
List.TrueForAll方法:确定是否List 中的每个元素都与指定的谓词所定义的条件相匹
配。
public bool TrueForAll(Predicate<T> match);
委托给拉姆达表达式:
E.g.:
bool flag = mList.TrueForAll(name =>
{
if (name.Length > 3)
{
return true;
}
else
{
return false;
}
}
);
Console.WriteLine("True for all: "+flag); //flag值为false
委托给一个函数,这里用到上面的ListFind函数:
E.g.:
bool flag = mList.TrueForAll(ListFind); //委托给ListFind函数
Console.WriteLine("True for all: "+flag); //flag值为false
这两种方法的结果是一样的。
List.FindAll方法:检索与指定谓词所定义的条件相匹配的所有元素。
public List<T> FindAll(Predicate<T> match);
E.g.:
List<string> subList = mList.FindAll(ListFind); //委托给ListFind函数
foreach (string s in subList)
{
Console.WriteLine("element in subList: "+s);
}
这时subList存储的就是所有长度大于3的元素
List.Take(n):获得前n行返回值为IEnumetable<T>,T的类型与List<T>的类型一样E.g.:
IEnumerable<string> takeList= mList.Take(5);
foreach (string s in takeList)
{
Console.WriteLine("element in takeList: " + s);
}
这时takeList存放的元素就是mList中的前5个
List.Where方法:检索与指定谓词所定义的条件相匹配的所有元素。
跟List.FindAll方法类似。
E.g.:
IEnumerable<string> whereList = mList.Where(name =>
{
if (name.Length > 3)
{
return true;
}
else
{
return false;
}
});
foreach (string s in subList)
{
Console.WriteLine("element in subList: "+s);
}
这时subList存储的就是所有长度大于3的元素
List.RemoveAll方法:移除与指定的谓词所定义的条件相匹配的所有元素。
public int RemoveAll(Predicate<T> match);
E.g.:
mList.RemoveAll(name =>
{
if (name.Length > 3)
{
return true;
}
else
{
return false;
}
});
foreach (string s in mList)
{
Console.WriteLine("element in mList: " + s);
}
这时mList存储的就是移除长度大于3之后的元素。
补充:比如要实现一个int 类型的动态二维数组。
要增加一行就matrix.add(new List<int>());
某行要增加数据就matrix[n].add(i);但是不建议这么操作。
List<List<int>> matrix = new List<List<int>>();
matrix.Add(new List<int>());
matrix[0].Add(1);
Console.WriteLine(matrix[0][0]);
Console.ReadKey();
List 与数组的相互转换
1.从string[]转List<string>
例如:string[] str={“1”,”2”};
List <string> list=new List<string>(str);
2.从List<string>转string[]
例如:List<string> list=new List<string>;
String[] str=list.ToArray();
二、序列化相关知识
序列化是将对象状态转换为可保持或传输的格式的过程。
与序列化相对的是反序列化,它将流转换为对象。
这两个过程结合起来,就使得数据能够被轻松地存储和传输。
为什么您想要使用序列化?有两个最重要的原因促使对序列化的使用:一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本;另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。
例如,序列化可用于在 中保存会话状态并将对象复制到 Windows 窗体的剪贴板中。
远程处理还可以使用序列化通过值将对象从一个应用程序域传递到另一个应用程序域中。
.NET Framework 提供两种序列化技术:
∙二进制序列化保持类型保真度,这对于在应用程序的不同调用之间保留对象的状态很有用。
例如,通过将对象序列化到剪贴板,可在不同的应用程序之间共享对象。
您可以将对象序列化到流、磁盘、内存和网络等等。
远程处理使用序列化“通过值”在计算机或应用程序域之间传递对象。
∙XML 序列化仅序列化公共属性和字段,且不保留类型保真度。
当您希望提供或使用数据而不限制使用该数据的应用程序时,这很有用。
由于 XML 是一个开放式标准,因
此,对于通过 Web 共享数据,它是一个有吸引力的选择。
SOAP 同样是一个开放式标准,这使它也成为一个颇具吸引力的选择。
在本系统的实现中,我们一般采用二进制的序列化,它将对象的状态存储到存储媒介中,对象的公共字段和私有字段以及类的名称(包括包含该类的程序集)都被转换为字节流,然后写入数据流。
在以后反序列化该对象时,创建原始对象的精确复本。
令一个类可序列化的最简单的方式是按如下所示使用 Serializable 属性标记它。
[Serializable]
public class MyObject {
public int n1 = 0;
public int n2 = 0;
public String str = null;
}
以下代码示例说明该类的实例是如何被序列化到一个文件中的。
MyObject obj = new MyObject();
obj.n1 = 1;
obj.n2 = 24;
obj.str = "Some String";
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();
该示例使用二进制格式化程序执行序列化。
您需要做的所有工作就是创建流的实例和您想要使用的格式化程序,然后对该格式化程序调用 Serialize 方法。
要序列化的流和对象作为参数提供给该调用。
尽管在此示例中并没有显式阐释这一点,但一个类的所有成员变量都将被序列化,即使是那些已被标记为私有的变量。
在此方面,二进制序列化不同于XMLSerializer 类,后者只序列化公共字段。
有关从二进制序列化中排除成员变量的信息,请参见有选择的序列化。
将对象还原回其以前的状态十分简单。
首先,创建用于读取的流和格式化程序,然后指示格式化程序反序列化该对象。
下面的代码示例说明如何执行上述的操作。
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
MyObject obj = (MyObject) formatter.Deserialize(stream);
stream.Close();
// Here's the proof.
Console.WriteLine("n1: {0}", obj.n1);
Console.WriteLine("n2: {0}", obj.n2);
Console.WriteLine("str: {0}", obj.str);
上面所用的 BinaryFormatter 非常有效,它生成了非常简洁的字节流。
通过该格式化程序序列化的所有对象都还可以通过该格式化程序被反序列化,这使该工具对于序列化将
在 .NET 平台上被反序列化的对象而言十分理想。
需要特别注意的是,在反序列化一个对象时不调用构造函数。
出于性能方面的原因对反序列化施加了该约束。
但是,这违反了运行库生成的与对象编写器有关的一些通常协定,并且开发人员应确保他们在将对象标记为可序列化时理解其后果。
课题二.连连看游戏
一、问题描述:
连连看游戏是一种源自台湾的桌面小游戏,自从流入大陆以来风靡一时,也吸引众多程序员开发出多种版本的“连连看”。
本课题要求学生能够完成基本的“连连看”功能设计与实现,当用户选中两张相同图片时,系统能够判定两个选中的图标是否能够连通,即可以通过不多于两个折点的折线连在一起。
二、基本要求
1.用户开始新游戏后,程序会载入图片,并通过特定的算法排列图片,并标记图片,以便判断图片是否相同。
记录图片坐标,以方便计算图片是否可以相连。
确定图片大小,以确定用户点击的是那张图片。
2. 用户通过鼠标进行选取图片,如果用户两次点击的图片不相同,那么系统认为用户没有点,让用户继续选取。
如果用户选择的是两张相同的图片,并且可以找到一条通路,仅通过不多于两个折点的连线将这两张图片相连,那么系统会消除这两张图片。
3.如果用户消除了所有界面上的图片,或者剩余的任意的一对图片均不符合消除的规则,则游戏结束
三、改进功能
1.用户也可使通过使用“炸弹”功能进行消除图片。
当用户使用“炸弹”道具,则当界面上有两张可以相连的相同图片时,程序自动消除他们;为增加游戏的挑战性,炸弹数目是有限的,默认数目为三,每使用一次“炸弹”功能,炸弹数目就会自动减一,当炸弹数目为零时(使用三次“炸弹”功能)。
“炸弹”不可使用。
2. 当系统开始游戏后,系统会在后台记录时间,如果在用户还没有消除所有图片时,系统时间到了,那么系统会弹出对话框,提示用户,时间到了,游戏失败。
如果在时间未到时,用户使所有的图片都消失(两两消除,直到最后一对),系统会提示用户游戏以通过,点“确定”后回到下一关界面。
3.可通过设置不同的消除策略使得游戏具有更好的趣味性,如使得图片下坠,往上漂移,中间靠拢等
4.其他可参考“连连看”百度词条:
/view/27579.htm
三、实现指导
1.游戏的主要框架:
本程序主要包含两大模块:即(1)表示层模块;(2)后台逻辑模块;
其中表示层模块可以理解为游戏的UI及一些游戏辅助效果,表示层模块中,重要的是实现游戏的布局地图,项目中实现中,游戏的布局将使用自定义View的方式,在屏幕上贴图实现。
而菜单模块及选关的dialog,只是为用户提供一些常见的选择,如重玩,过关继续,音效开关等等,为了有一个更好的用户交互环境,dialog的实现将通过自定义dialog的方式。
而游戏音效是MediaPlayer在不同的状态场景下播放不同的游戏音效。
而后台逻辑模块中,即时对于程序计算的实现与程序各种状态的监听,将是整个程序运行的基础。
此模块中将实现对于游戏剩余时间限制和游戏状态的监听与处理。
对于游戏剩余时间的监听,将开启单独的线程进行处理,从而不至于影响主程序逻辑的运行;游戏的状态的监控处理中,将会实现对于连通的两个图标的消除(即游戏界面的更新),游戏输赢的监听判断,游戏暂停与否等(暂停状态需要同时将剩余时间暂停,而时间监听线程需要知道所处状态,此二者紧密联系)。
对于本程序中最重要的还是程序中核心算法模块的实现,在游戏中,最主要的算法是判断两个选中的图标是否能够连通,其余两个算法也依赖于此算法而进行。
下面着重介绍一下连接算法:
在介绍连接算法之前,先简单介绍一下连连看的布局算法,为了简单起见,我们使用4*4的棋盘,假设图片有四种。
首先在程序初始化时,我们先将要加载的图片在棋盘上按序绘制出来,注意每一种图标我们绘制的时候需要一次性绘制两次,这样,才能保重绘制出来的每种图标的个数都是偶数个。
假设最初如下图(1):
图(1)最初绘制图(2)调换后棋盘
这样绘制后,我们进行一次遍历,随机的调换棋盘中的图标(是现有棋盘中的图标之间的调换,并不是更改成为其他的图标)。
经过调换的棋盘可能如图(2)所示这样就完成了棋盘的初始化,当然我们的棋盘在最外面一层中是不添加图标的,为的是我们连线时候能够使用最外层画线,而不会出现穿过图标画线的情况,棋盘如下图:
2.连接算法的原理与实现:
首先两个图标能够连接的充分条件是:(1)两个图标是相同的;(2)两个图标之间有一条路相连,其中这条“路上”没有其他的图标“阻碍”;(3)这一条路不能有两个以上的拐角;满足这三个条件即可认为两个图标是相连通的;对于连通的判断中,图标连通时有三种情况,分为以下:
(1)直线型:这就是横向或者是纵向方向判断即可,这种情况最容易判断,只要两者之间没有其他图标即可,就不多说;
(2)一折型:其实在两个选中图标确定的两个对角顶点画一个矩形,若是其余两个顶点中有能够满足与两个选中图标都能够“直线型”相连的,即可认为此两个选中图标可以相连,实例如下,判断两个红色的图标相连的情况:
一折型的示例
(3)二折型:对于二折型的判断是重点。
判断二折型主要是做两个方向的扫描,即横向扫描与纵向扫描:
首先说横向扫描,如下图所示:
横向扫描中:首先将两个需要判断的比图标进行横向的扩展,扩张规则是在没有遇到其他图标时一直扩展,直到遇到此行的其他图标或者到达棋盘的边缘,扩展后的点如图中X表示,如果扩展后的点种能够存在两点满足直线型相连通的情况,即可判断两个图标是可以相连通的,连通的画线也是根据这两个辅助点相连而成的;
类似的,对于纵向扫描中:
连连看连接算法到此也算完成了,至于“炸弹”的hint帮助算法,判断当前棋盘是否还有解的算法都是依赖于此算法。
四.补充知识:
本课题在.net平台下开发的Windows Form程序,故还须学习Win Form窗体编程基础知识,绘图基础知识等,详见课件及课堂示例代码
21。