• 背单词软件-设计与实现


    背单词软件-设计与实现

    2014-08-28

    源代码下载:Code-ReciteWord.zip

    三层结构

      数据访问层

      业务逻辑层

      表现层

    数据结构

      永久层

      业务逻辑层

    难点与解决方案

      Recite的自动播放功能

      Recite的播放暂停功能

      窗口之间传递数据

      设置信息处理

    经验与教训

      永久层数据结构的问题

      待研究

    三层结构


     返回

    三层结构优点:职责明确,易于理解,提高代码重用率,便于改动。

    三层结构缺点:上层依赖下层,下层修改,上层也得相应修改。

    图1 Solution

    从图1可知,软件是三层结构,分别是表现层、业务逻辑层、数据访问层。三层结构都是上层调用下层:表现层->业务逻辑层->数据访问层,表现层->数据访问层亦可。

    数据访问层 

    数据访问层提供对永久层数据(比如:文件,数据库)的简单操作(增删改查)。

    注意:这一层操作逻辑一定要简单,便于业务逻辑层调用。

    图2是TaskDataAccess的方法和属性,只有对xml文件增删改查、创建、保存操作

    图2 TaskDataAccess

    业务逻辑层

    如果数据访问层方法的比较简单,那么数据访问层的方法相对复杂,粒度更大,加入更多判断和相关操作。

    下面是TaskDataControl.cs的主要的公共方法:

     1 public void Refresh();
     2 public List<string> ReciteUnitNow();
     3 public void AutoChangeRecitePeriod(string unitName, DateTime dt);
     4 public void RedoRecitePeriod(string unitName);
     5 public void RedoReciteUnit(string unitName);
     6 public void ReDoFilterUnit(string unitName);
     7 public void CompleteRecitePeriod(string unitName);
     8 public void CompleteReciteUnit(string unitName);
     9 public void DeleteUnit(string unitName);
    10 public void FilterUnit(string unitName);
    11 public void ReciteUnit(string unitName);
    View Code

    表现层

    表现层包括界面设计以及状态。以frmReciteWord_Task为例,它包括界面frmReciteWord_Task、幕后代码,还有TaskDiplay.cs。其中,TaskDisplay.cs主要负责界面状态,把它分离出来,主要是为了方便理解,和重用。

    TaskDisplay.cs通过frmReciteWord_Task.cs通过参数传入的控件引用来控制控件状态。(这可能不是很好的实现-待定

    数据结构


    返回

    永久层

    文件TaskFile.xml

     1 <?xml version="1.0"?>
     2 <Units>
     3   <Unit Name="Lession01" UnitStatus="Complete" />
     4   <Unit Name="Lession02" UnitStatus="Start">
     5     <Detail Period="2" PeriodStatus="Complete" CurrentReciteTime="8/27/2014 11:04:26 PM" />
     6   </Unit>
     7   <Unit Name="Lession03" UnitStatus="Start">
     8     <Detail Period="2" PeriodStatus="Start" CurrentReciteTime="8/27/2014 11:04:26 PM" />
     9   </Unit>
    10   <Unit Name="Lession04" UnitStatus="Start">
    11     <Detail Period="0" PeriodStatus="Start" CurrentReciteTime="8/27/2014 11:01:26 PM" />
    12   </Unit>
    13   <Unit Name="Lession05" UnitStatus="Start">
    14     <Detail Period="0" PeriodStatus="Start" CurrentReciteTime="8/27/2014 09:01:26 PM" />
    15   </Unit>
    16   <Unit Name="Lession06" UnitStatus="Filter" />
    17   <Unit Name="Lession07" UnitStatus="Raw" />
    18 </Units>
    View Code

    我们可以看到,永久层的属性有:Name, UnitStatus, Period, PeriodStatus, CurrentReciteTime

    业务逻辑层

    文件TaskDataControl.cs

    图3 Task Business data constructor

    业务逻辑层属性有:Name, UnitStatus, Period, PeriodStatus, CurrentReciteTime, Priority, NextReciteTime。其中,Priority, NextReciteTime是计算出来的。

    在这些属性中,UnitStatus和PeriodStatus比较相似,他们的值,我用枚举一一列出来了:

    1 enum EnumTaskUnitStatus { Start, Complete, Raw, Filter }
    2 enum EnumTaskPeriodStatus { Start, Complete }

    我们可以看到两个状态中都有Start和Complete。因为要把课文中单词永久记住,需有多个周期的记忆、遗忘。UnitStatus的Start和Complet指的是开始学习,到最后结束;而PeriodStatus的Start和Complete只当前周期的开始结束。

    注意:数据结构中,对某个属性理解比较模糊时,一定要停下来思考清楚。清晰的设计,带来轻松的实现。

    当然,永久层的数据结构和业务逻辑层的数据结构不必完全一一对应,视实际情况而定。 

    难点与解决方案


     返回

    Recite的自动播放功能

    当时想用线程实现,然后在线程中直接更改winForm中控件的值来更改winForm的界面显示。但这样做会抛出异常:

    Cross-thread operation not valid: Control accessed from a thread other than the trhead it was created on. 

    解决方案是通过control.invoke来更改控件的值。具体原理 横刀天笑 写的博客 WinForm二三事:

    WinForm二三事(一)消息循环

    WinForm二三事(一)补遗

    WinForm二三事(二)异步操作

    WinForm二三事(三)Control.Invoke&Control.BeginInvoke

    Recite的播放暂停功能

    上面提到自动播放是通过thread实现的,原本暂停功能想通过thread自带方法suspend()实现,但高版本net framework mark这个方法depreciated。因此,另找出路,用AutoResetEvent类来通知thread wait or contiune。看代码示例:

     1     public partial class Form1 : Form
     2     {
     3         System.Windows.Forms.Timer tm = new System.Windows.Forms.Timer();
     4 
     5         //through autoEvent notify a thread waiting or continue
     6         AutoResetEvent autoEvent = new AutoResetEvent(false);        
     7 
     8         public Form1()
     9         {
    10             InitializeComponent();
    11             ProgressBar.CheckForIllegalCrossThreadCalls = false;
    12 
    13             
    14             tm.Interval = 1;
    15             tm.Tick += new EventHandler(tm_Tick);            
    16         }
    17 
    18         void tm_Tick(object sender, EventArgs e)
    19         {
    20             autoEvent.Set(); //allow waiting thread proceed    
    21         }
    22         
    23 
    24         //method for thread   
    25         private void DoWork()
    26         {
    27             while (progressBar1.Value < progressBar1.Maximum)
    28             {
    29                 progressBar1.PerformStep();
    30                 System.Threading.Thread.Sleep(100);
    31                 autoEvent.WaitOne();  //Block current thread until autoEvent.Set()    
    32             }
    33         }
    34 
    35         private void btnStart_Click(object sender, EventArgs e)
    36         {
    37             tm.Start();
    38 
    39             Thread t = new Thread(DoWork);
    40             t.Start();
    41             t.Suspend();
    42         }
    43 
    44         private void btnSuspend_Click(object sender, EventArgs e)
    45         {
    46             tm.Stop();
    47         }
    48 
    49         private void btnResume_Click(object sender, EventArgs e)
    50         {
    51             tm.Start();
    52         }   
    53     }
    View Code

    窗口之间传递数据

    这里主要通过两种方法:

    • 通过公开的属性
    • 单独设个类,通过静态set和get方法 

    设置信息处理

    设置信息见上一篇设置功能,一般可以存放在xml配置文件中。但为了程序更简单,把这些信息放在注册表中。set,get注册表的静态方法都放在Utility.cs文件中。

    经验于教训


     返回

    永久层数据结构的问题

    由于Unit的Priority的确定跟UnitStatus,PeriodStatus,NextReciteTime相关,见上一篇任务管理功能表三 Priority的取值。而且,有这样的设计,当CurrentReciteTime的PeriodStatus为‘Complete’,NextReciteTime的背诵时间已到,会自动把CurrentReciteTime改为NextReciteTime,PeriodStatus改为‘Start’,NextReciteTime的值在根据CurrentReciteTime+EbbinghausPeriod确定。因此觉得NextRecitTime应该放在永久层中。

    但在调整Ebbinghaus Period的时候,发现已确定下来的NextReciteTime很难修改。于是,想到是数据结构出现问题。NextReciteTime可以根据CurrentReciteTime+EbbinghausPeriod确定,因此,只需要保存CurrentReciteTime就可以了。

    心得:

    • 清晰地设计带来简单的实现。
    • 代码越写越明,如果发现难以进行,多半是原来设计逻辑有问题。

    待研究

    Winform程序如果不在幕后代码partial文件中出现的异常,比如在TaskDataAccess.cs中出现outOfIndex,null object reference异常,不会在界面上显示异常。

    参考:

    [1] 三层结构_百度百科

  • 相关阅读:
    My Tornado Particle Effect
    [zz] 海洋环境的光能传递
    一道算法题
    Alembic
    一些莫名其妙的东东
    Python Q&A
    <<Exceptional C++>> notes
    CG Rendering v.s. Browser Rendering
    Modo
    Katana
  • 原文地址:https://www.cnblogs.com/Ming8006/p/3941928.html
Copyright © 2020-2023  润新知