zoukankan      html  css  js  c++  java
  • 4.3 试题库的设计和实现(借鉴String类实现减少数据冗余)

    试题管理是整个系统的核心。为了尽量的从逻辑上组织试题和尽量的减少数据冗余。在线考试系统把领域对象划分地非常细。下图4-7说明了试题库相关领域类的设计


    图4-7 试题库模块类图

    这些领域类最终都要保存到数据库表里。为了方便管理将所有QuestionContent类的所有子类全部保存到QuestionContent表中。并用Type字段来区分不同的题型。Choice侧以XML的格式保存到Choices字段中。并编写了自定义的ChoiceList类来负责Choice集合从字段中的XML格式到对象集合的转换

    namespace ExaminationSystem.DAL.UserType
    {
        public class ChoiceList : IUserType
    {
            
    //代码省略…
    }

    }

    该类在领域类的NHibernate映射文件中配置使用。告诉NHibernate框架如何处理Choices字段。下图4-8是QuestionContent表的设计

    图4-8 QuestionContent表



    为了支持章节的树形模型。也就是说每个章节可以包含多个子章节。我们的Chapter表中有一个ParentID字段用来指明该章节的父章节。如下图4-9



    4-9 Chapter

    Subject表如下图4-10:

    4-10 Subject

    有了领域类的关系和对应的表关系。当需要加载领域类的时候必须把表里的记录转换成内存中的对象。当要保存领域类时候又必须把内存对象保存成记录。这些任务都有NHibernate自动来完成。我们要做的就是告诉NHibernate如何来做这两种不同风格数据表现的转换和映射。我们通过映射文件来告诉NHibernate那个类应该保存到那个表里。那个属性应该保存在那个字段里或者保存到其他表里。反之亦然!下面就用Subject,Chapter来说明类到表是如何映射的。

    <?XML version="1.0" encoding="utf-8" ?>
    <hibernate-mapping XMLns="urn:nhibernate-mapping-2.2" assembly="ExaminationSystem.BLL" namespace="ExaminationSystem.BLL.Domain">
      
    <class name="Subject" table="Subject" lazy="false">
        
    <id name="ID" column="ID">
          
    <generator class="identity" />
        
    </id>
        
    <property name="Name" column="Name" />
        
    <bag name="Chapters" table="Chapter" inverse="true" cascade="all">
          
    <key column="SubjectID"/>
          
    <one-to-many class="Chapter"/>
        
    </bag>
        
    <bag name="PaperStrategys" table="PaperStrategy" inverse="true" cascade="all">
          
    <key column="SubjectID"/>
          
    <one-to-many class="PaperStrategy"/>
        
    </bag>
      
    </class>
    </hibernate-mapping>

    以上是Subject.hbm.XML的完整代码其中

    <class name="Subject" table="Subject" lazy="false">

    节点告诉NHibernate Subject类放到Subject表。

    <id name="ID"column="ID">

          <generator class="identity" />

    </id>

    告诉NHibernateID属性是Subject的标识属性,对应到表ID自增长字段。

    <property name="Name" column="Name" />

    告诉Nhibernate Name属性保存到Name字段。

        <bag name="Chapters" table="Chapter" inverse="true" cascade="all">

          <key column="SubjectID"/>

          <one-to-many class="Chapter"/>

    </bag>

    这里的Chapters属性是一个Chapter对象的集合。这些Chapter对象保存到Chapter表中。并且是通过Chapter表的SubjectId类来标识Chapter表的记录属于那个Subject对象。这里只给了Subject类的映射文件。并且只是简单的说明了一般属性的映射和一到多关系的映射。关于更加复杂的类继承关系映射和自定义用户类型的映射、多对多关联的映射、延迟加载等更高级的主题请参考NHibernate官方文档。通过映射文件NHibernate完成了类到表,表到类的转换。让我们可以用面向对象的方式设计和实现系统。使系统更加的健壮和灵活。下面介绍关于QuestionContent及其所有子类的更新和删除问题。为了减少数据冗余我们将试题分成了两种不同的概念。分别是QuestionQuestionContent Question代表的是试卷上的试题。下面用类图来说明QuestionQuestionContent的关系。如图4-11:






    图4-11 Question和QuestionContent关系类图


    QuestionContent包含了题目的内容和标准答案、难度等信息。Question类包含分值和学生答案和得分。这样分离可以使每个试题的内容和标准答案只保留一份。比如当有100张试卷。且每张试卷都只有一样的一道题。会有100个不同Question对象.但是只要一个QuestionContent对象。这样设计无疑会大大的减少数据的冗余。但是也带来了一些问题。比如,如果试卷Paper上的Question对象上引用了一个QuestionContent的对象。我们将无法删除这个QuestionContent对象。因为Question对象对QuestionContent对象的引用在数据库中是通过外键来引用的。如果我们删除一个被引用着的QuestionContent对象将违反外键约束。而且即使没有外键约束在逻辑上也不应该删除修改这个QuestionContent对象。假设一个学生做过了一个题A,题的内容是B。学生答对了该题目。学生根据分值得到了100分。如图4-12:


    图4-12 Question和Content对象图

    后来老师忽然觉得这个题目过于简单。于是将试题内容改了改。B对象的Content变成了1*1=?。答案自然也变成了1。如果老师改完题目后又直接去改卷子。发现学生竟然答案是2于是无情的就给了0分。当然这些假如有些勉强,但是问题是在逻辑上一旦题目在考试卷上出现过它就没办法改变。似乎Question应该单独拥有一个QuestionContent对象。这样在修改题库里QuestionContent才不会影响Question对象。但是如果这样就会有很多的重复的QuesitonContent对象。从逻辑上分析QuesitonContent 对象确实应该是一个值类型的对象。而不应该是引用类型。但是作为引用类型来处理确实能省掉很多的内存和数据库空间。只是在修改的时候才会出现问题。在线考试系统的确实是用引用类型的方式来处理QuestionContent的。但是怎样才能避免上面提到的修改问题呢。也就是说如何让一个引用类型的对象具有值类型的行为。这里借鉴了.net类库里对Sting类型的实现方法。就是保证QuestionContent 具有不变性。当在题库更新一个QuestionContent对象的时候就新创建一个QuestionContent然后在将旧的对象和Chapter对象直接的关系给移除。这样从逻辑上就把旧的QuestionContent给删除了。但是又不影响Quesiton。删除时就是把QuestionContent对象和Chapter对象的关系解除。当然如果这个QuestionContent 对象从来没有被Quesiton对象引用过。我们只解除和Chapter的关系而不是物理删除它会存在很多垃圾数据。这些数据将永远存在数据库中,却永远不会被系统使用。内存中的垃圾数据可以有垃圾回收机制来回收。但是数据库中的却没有,所以我们还应该定期的清理那些无用的数据。

     public class QuestionContentService
    {
    //更新试题操作
            public void Update(QuestionContent questionConent)
            
    {
                
    //新建一个试题,并删除老的试题
                QuestionContent newQuesitonContent = new QuestionContent();
                newQuesitonContent.Chapter 
    = questionConent.Chapter;
                newQuesitonContent.Content 
    = questionConent.Content;
                newQuesitonContent.Answer 
    = questionConent.Answer;
                Dao.Save(newQuesitonContent);

                Delete(questionConent);
            }

            
    public void Delete(QuestionContent quesitonContent)
            
    {
                    
    //删除操作只是将试题和章节解除关联
                    questionContent.Chapter=null;
                    Dao.Update(questionContent);
            }

            
    public void ClearUselessQuestionContent(QuestionContent quesitonContent)
            
    {
                
    //如果试题没有被使用就从数据库中删除,否则什么都不做
                if(questionContent is not used)
                        Dao.Delete(quesitonContent);
                
    else
                    
    //Do nothing
            }

        }

    这样就可以即减少数据冗余又保证系统的正确性!





  • 相关阅读:
    RHEL7管道与重定向
    RHEL7软件包管理
    RHEL7用户管理
    RHEL7文件管理
    RHEL7文件查找
    RHEL7文件权限
    RHEL7文件归档与压缩
    RHEL7进程管理
    博客园样式美化
    flask+python页面修改密码功能
  • 原文地址:https://www.cnblogs.com/xhan/p/1202965.html
Copyright © 2011-2022 走看看