思路:
方案一:在策略模式+反射这篇博客里面我们说道为了封装题库,D层写了一个抽象类,包括题型的所有方法,子类重写父类的方法。然后再B层利用反射找到D层的具体题型。具体实现可以参考上篇博客,在这里就不在赘述。这样实现就有一个问题,比如所有的题型都需要生成相同的答题记录表,这样每个子类都需要重写父类的方法,造成大量的代码冗余。
方案二:采用继承,将公共的方法放到父类里面,子类继承父类同时拥有自己新的方法。在接口层写一个公共的接口,包括所有的方法。这样我们就可以将一些共同的方法都放到父类里面,即使有的题型需要的方法体不同,只要把父类的方法定义为virtual类型的,在子类里面重写一下就可以了。说的有点乱,下面来看代码:
///
// QuestionManageBLL.cs
//作者: 王永俊
//小组:提高班-考试系统3.0版-教师端
//说明:试题抽象工厂+反射——策略模式
//创建日期:4-11月-2013 20:15:14
//版本号:
///
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using IDAL;
namespace QuestionManageBLL
{
//调用这个类时,QuestionType和TableName是必须传的参数(及题型Value值和数据库表名)
public class QuestionContextBLL
{
//获取程序集名称
private static readonly string AssemblyName = "QuestionManageDAL";
//实例化一个试题公共对象
IQuestionCommonDAL question = null;
/// <summary>
///反射到QuestionManagerDAL里面的类
/// </summary>
/// <param name="QuestionType">题型Value值</param>
//利用反射取得具体的题型
public QuestionContextBLL(Hashtable map)
{
string className = AssemblyName + "." + map["QuestionType"].ToString() + "DAL";
question = (IQuestionCommonDAL)Assembly.Load(AssemblyName).CreateInstance(className);
}
/// <summary>
///二次登陆
/// </summary>
/// <param name="TableName">表名</param>
/// <param name="TableNameRecord">答题记录表名</param>
/// <param name="StudentID">学号</param>
/// <param name="ExamID">考试ID</param>
public DataTable GetTestExamResult(Hashtable map)
{
return question.GetTestExam(map);
}
/// <summary>
///创建题型表
/// </summary>
/// <param name="TableName">表名</param>
public bool CreateQuestionTable(Hashtable map)
{
return question.CreateQuestionTable(map);
}
/// <summary>
/// 是否存在该记录
/// </summary>
/// <param name="TableName">表名</param>
/// <param name="QuestionContent">题干</param>
public bool ExistQuestion(Hashtable map)
{
return question.ExistQuestion(map);
}
///
// IQuestionManageDAL.cs
//作者: 王永俊
//小组:提高班-考试系统3.0版-教师端
//说明:试题公共类——接口
//创建日期:3-11月-2013 20:15:14
//版本号:1.0
///
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
namespace IDAL
{
public interface IQuestionCommonDAL
{
/// <summary>
///二次登陆
/// </summary>
/// <returns></returns>
DataTable GetTestExam(Hashtable map);
/// <summary>
/// 根据章节查询题库信息
/// </summary>
DataTable SelectQuestionbyChapter(Hashtable map);
/// <summary>
/// 根据是否可用查询题库信息
/// </summary>
DataTable SelectQuestionbyIsvalid(Hashtable map);
//
// QuestionCommonDAL.cs
//作者: 王永俊
//小组:提高班-考试系统3.0版-教师端
//说明:试题公共类——父类
//创建日期:3-11月-2013 20:15:14
//版本号:1.0
///
using System;
using System.Data;
using System.Text;
using System.Data.SqlClient;
using DBUtility;//Please add references
using System.Collections;
using IDAL;
namespace SQLServerDAL
{
//试题父类,一些公共方法!
public class QuestionCommonDAL
{
/// <summary>
///二次登陆
/// </summary>
/// <returns></returns>
public virtual DataTable GetTestExam(Hashtable map)
{
DataTable dt;
string strSql = "SELECT j.*,t.EXAMANSWER FROM " + map["TableNameRecord"].ToString() + " t inner join " + map["TableName"].ToString() + " j On j.QuestionID=t.QuestionID WHERE t.StudentID=@StudentID and ExamID=@ExamID";//and j.Degree=@Degree";
SqlParameter[] parameters = {
new SqlParameter("@StudentID", map["StudentID"].ToString ()),
new SqlParameter("@ExamID", map["ExamID"].ToString ())};
dt = DbHelperSQL.Query(strSql.ToString(),parameters ).Tables[0];
return dt;
}
/// <summary>
/// 题库中是否存在该记录
/// </summary>
public bool ExistQuestion(Hashtable map)
{
StringBuilder strSql = new StringBuilder();
strSql.Append("select count(1) from " + map["TableName"].ToString());
strSql.Append(" where QuestionContent=@QuestionContent ");
SqlParameter[] parameters = {
new SqlParameter("@QuestionContent", map["QuestionContent"].ToString())};
bool a= DbHelperSQL.Exists(strSql.ToString(), parameters);
return a;
}
/// <summary>
/// 根据章节查询题库信息
/// </summary>
public virtual DataTable SelectQuestionbyChapter(Hashtable map)
{
DataTable dt;
StringBuilder strSql = new StringBuilder();
strSql.Append("select [QuestionID] as '题号' ,[ChapterID] as '章节',[QuestionTypeID] as '题型',[Degree] as '难度等级',[Fraction] as '总分值',[QuestionContent] as '主题干',[CorrectAnswer] as '标准答案',[IsValid] as '有效性',[Remark] as '备注'");
strSql.Append(" FROM " + map["TableName"].ToString());
strSql.Append(" where ChapterID=@ChapterID order by QuestionID");
SqlParameter[] parameters = {
new SqlParameter("@ChapterID",map["ChapterID"].ToString () )};
dt = DbHelperSQL.Query(strSql.ToString(),parameters ).Tables[0];
return dt;
}
/// <summary>
/// 根据试题ID查询题库信息
/// </summary>
public virtual DataTable SelectQuestionbyQuestionID(Hashtable map)
{
try
{
DataTable dt;
StringBuilder strSql = new StringBuilder();
strSql.Append("select [QuestionID] ,[ChapterID] ,[QuestionTypeID] ,[Degree] ,[Fraction] ,[QuestionContent] ,[CorrectAnswer],[IsValid] ,[Remark] ");
strSql.Append(" FROM " + map["TableName"].ToString());
strSql.Append(" where ");
if (map["QuestionID"].ToString() != "")
{ strSql.Append(" QuestionID=@QuestionID and "); }
if (map["IsValid"].ToString() != "")
{ strSql.Append("IsValid=@IsValid "); }
strSql.Append(" order by QuestionID");
SqlParameter[] parameters = {
new SqlParameter("@QuestionID", map["QuestionID"].ToString ()),
new SqlParameter("@IsValid", map["IsValid"].ToString ())};
dt = DbHelperSQL.Query(strSql.ToString(),parameters ).Tables[0];
return dt;
}
finally
{
}
}
///
// ClozeDAL.cs
//作者: 王永俊
//小组:提高班-考试系统3.0版-教师端
//说明:完型填空
//创建日期:3-11月-2013 20:15:14
//版本号:1.0
///
using System;
using System.Data;
using System.Text;
using System.Data.SqlClient;
using DBUtility;//Please add references
using System.Collections;
using IDAL;
using SQLServerDAL;
namespace QuestionManageDAL
{
public class ClozeDAL : QuestionCommonDAL, IQuestionCommonDAL
{
#region BasicMethod
/// <summary>
/// 根据课程创建完型填空题表
/// </summary>
/// <param name="courseName">哈希表</param>
public bool CreateQuestionTable(Hashtable map)
{
try
{
//创建主表
string strSql = @"IF object_id('" + map["TableName"].ToString() + "') IS NULL Create Table " + map["TableName"].ToString() + "(QuestionID int, " +
"Scope varchar(20) ," +
"BookName varchar(20) ," +
"ChapterID varchar(20) ," +
"QuestionTypeID varchar(20)," +
"Degree int," +
"Fraction float," +
"QuestionContent varchar(Max)," +
"IsValid varchar(10) ," +
"AddUser varchar(20)," +
"Timestamp varchar(20)," +
"Remark varchar(Max)," +
"Other1 varchar(50)," +
"Other2 varchar(50) constraint PK_" + map["TableName"].ToString() + " primary key (QuestionID))";
int rows = DbHelperSQL.ExecuteSql(strSql.ToString());
//创建从表
string strSql1 = " Create Table " + map["TableName"].ToString() + "_Detail " + "(ID int IDENTITY(1,1)," +
"QuestionID int ," +
"QuestionNo int ," +
"Score float ," +
"Answer1 varchar(Max)," +
"Answer2 varchar(Max)," +
"Answer3 varchar(Max)," +
"Answer4 varchar(Max)," +
"CorrectAnswer varchar(Max)," +
"Other1 varchar(50)," +
"Other2 varchar(50) constraint PK_" + map["TableName"].ToString() + "_Detail " + " primary key (ID))";
int rows1 = DbHelperSQL.ExecuteSql(strSql1.ToString());
if (rows != 0)
{
return true;
}
else
{
return false;
}
}
finally
{
}
}
/// <summary>
///二次登陆
/// </summary>
/// <returns></returns>
public override DataTable GetTestExam(Hashtable map)
{
try
{
DataTable dt;
string strSql = "SELECT m.*, j.*,t.EXAMANSWER FROM " + map["TableNameRecord"].ToString() + " t inner join " + map["TableName"].ToString() + "_Detail " + "j On j.ID=t.QuestionID inner join " + map["TableName"].ToString() + " m On m.QuestionID=j.QuestionID WHERE t.StudentID=@StudentID and ExamID=@ExamID";//and j.Degree=@Degree";
SqlParameter[] parameters = {
new SqlParameter("@StudentID", map["StudentID"].ToString ()),
new SqlParameter("@ExamID", map["ExamID"].ToString ())};
dt = DbHelperSQL.Query(strSql.ToString(), parameters).Tables[0];
return dt;
}
finally
{
}
}
#endregion BasicMethod
}
}
讲解:
QuestionManageBLL层基本上没有变化,只是通过接口反射。IQuestionCommonDAL接
口定义方法的规则,必须包括所有的方法。父类QuestionCommonDAL包括所有题型共有
的方法,比如:对题库的所有操作。以及大部分题型共有的方法,其他题型只需要重写父
类的方法。这样可以大量的减少代码的重复性。具体题型ClozeDAL类,继
承QuestionCommonDAL父类同时实现接口 IQuestionCommonDAL。他既有自己的新方
法CreateQuestionTable也继承了父类的ExistQuestion方法,重写了GetTestExam方法。这
样就让整个题库更加的内聚而对外更加灵活。比如需要添加一些共同的方法,直接添加
到父类就行。而子类里面不用更改。
总结:
对整个封装是一步步的完善的,也更好的理解了抽象类和继承以及接口。抽象类是
为了实现代码的复用和管理,体现OOP的继承特性,子类都有相同的方法,并且方法体
都不同可以选择抽象类。普通类的继承可以精炼你的代码,提高代码的复用。并且提高
整个系统的灵活性。而接口其实就是为我们的D层类订了一个规范,所有的子类都要按
照这个规范来,体现多态的思想。总体来说:当你写的新类和已有的类很相似时就用抽
象类,在很多类很相似时就给他们加一个相同的父类,其实抽象类及接口本质上都是父
类。
问题:
现在基本功能实现了,可是这样有个问题就是只要添加新的题型,无论他和以前的题
型多么相似,我都需要给它添加一个新类,进行封装。