软件系统设计-设计(5) 表驱动、POP、MVC
2023-08-19 00:15:38 # NJU # 软件系统设计

1. 表驱动

  1. 表驱动法是一种编程模式(scheme):从表里面查找信息而不使用逻辑语句(if和case)
  2. 表驱动法适用于复杂的逻辑
  3. 表驱动法的另一个好处是可以将复杂逻辑从代码中独立出来,以便于单独维护

1.1 表驱动示例

  1. Java示例:使用复杂的逻辑对字符分类
1
2
3
4
5
6
7
8
9
10
11
12
if ((('a' <= inputChar) && (inputChar <= 'z'))||
(('A' <= inputChar) && (inputChar <= 'Z'))) {
charType = CharacterType.Letter;
}else if ((inputChar==' ')||(inputChar==',')||
(inputChar == '.')||(inputChar == '!')||
(inputChar == '(')||(inputChar == ')')||
(inputChar == ':')||(inputChar == ';')||
(inputChar == '?')||(inputChar == '-')){
charType = CharacterType.Punctuation;
}else if (('0' <= inputChar)&&(inputChar <= '9')) {
charType = CharacterType.Digit;
}
  1. 构造一个查询表:把每一个字符的类型保存在一个字符编码访问的数组
  2. Java示例:使用查询表对字符分类chartype = charTypeTable[inputChar];

1.2 使用表驱动法的两个问题

  1. 在表里存放什么信息:主要存放的是数据,但在一些特殊情况下也存放动作
  2. 如何快速从表中查询条目
    1. 直接访问(Direct access)
    2. 索引访问(Indexed access)
    3. 阶梯访问(Stair-step access):连续性条件方法

1.3 直接访问表

所谓”直接访问”是指通过索引值(如下标)可以直接从表中找到对应的条目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'VB示例:确定各月天数的笨拙做法
If (month = 1) Then
days = 31
ElseIf (month = 2) Then
days = 28
ElseIf (month = 3) Then
days = 31
...
ElseIf (month = 11) Then
days = 30
ElseIf (month = 12) Then
days = 31
End If

' 你需要首先创建出这张表用来存放各个月份的天数
' VB示例:确定各月天数的优雅做法
Dim daysPerMonth() As Integer = _
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
days = daysPerMonth(month-1)

1.4 索引访问表

  1. 当无法直接从表中查询需要的条目时,就需要借助其他办法先获取表键值
  2. 索引访问的方法
    1. 就是先用一个基本类型的数据从一张索引表中查出一个键值,然后再用这一键值查出你感兴趣的主数据
  3. 索引表是一种间接访问的技术

示例

  1. 假设你经营着一家商店,有大约 100 种商品。再假设每种商品都有一个 4 位数字的物品编号, 其范围是 0000 到 9999
    1. 如果你想用这个编号作为键值直接查询一张描述商品信息的表,那么就要生成一个具有 10000 条记录的访问表
  2. 利用索引表
    1. 如果用这个编号作为键值直接查询一张描述商品信息的表,那么就生成一个具有 10000 条记录的索引数组(从0到9999)
    2. 该数组中除了与你商店中的货物的标志相对应的100条记录以外,其余记录都是空的索引

image-20230612121102830

索引访问技术的主要优点:

  1. 如果主查询表中的每一条记录都很大,那么索引数组就可以节省很多空间
    1. 一般而言索引表中的每条记录需要占用2 ~ 4字节
  2. 即使你用了索引以后没有节省内存空间, 操作位于索引中的记录有时也要比操作位于主表中的记录更方便更廉价
  3. 编写到表里面的数据比嵌入代码中的数据更容易维护

1.5 阶梯访问表

  1. 阶梯访问方法不像索引结构那样直接,但是它要比索引访问方法节省空间

  2. 阶梯结构的基本想法

    1. 通过确定每项命中的阶梯层次确定其归类

    image-20230612121404381

示例

如果你正在开发一个等级评定的应用程序,按照如下等级区间对分数定级

  1. 你不能用简单的数据转换函数来把表键值转换为 A 至 F 字母所代表的等级
  2. 用索引也不合适,因为这里用的是浮点数

使用阶梯方法

  1. 把每一区间的上限写入一张表里, 然后写一个循环,按照各区间的上限来检查分数
    1. 当分数第一次超过某个区间的上限时, 你就知道相应的等级了
    2. 区间表:{ 50.0, 65.0, 75.0, 90.0, 100.0 }
  2. Visual Basic示例:阶梯表查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
' set up data for grading table
Dim rangeLimit() As Double = { 50.0, 65.0, 75.0, 90.0, 100.0 }
Dim grade() As String = { "F", "D", "C", "B", "A" }
maxGradeLevel = grade.Length - 1
...
' assign a grade to a student based on the student's score
gradeLevel = 0
studentGrade = "A"
While ( gradeLevel < maxGradeLevel )
If ( studentScore < rangeLimit( gradeLevel ) ) Then
studentGrade = grade( gradeLevel )
End If
gradeLevel = gradeLevel + 1
Wend

注意事项

  1. 注意端点
  2. 二分查找取代顺序查找

2. Patterns of Patterns

2.1 Working together

使用模式的最佳方式之一是让他们走出家门,这样他们就可以与其他模式互动。 你使用模式越多,你就会越多地看到它们一起出现在你的设计中。对于在设计中协同工作的一组模式,可以应用于许多问题,我们有一个特殊的名称:复合模式(Compound Pattern)。没错,我们现在说的是模式的模式!

  • 模式经常一起使用,并结合在同一个设计解决方案中。
  • 复合模式将两个或多个模式组合成解决重复出现或一般问题的解决方案。

2.2 Duck reunion

  1. 实现 Quackable 接口
  2. a Goose class:需要被 simulation 使用,使用适配器模式
  3. 统计鸭叫的次数(要求不能修改原有的代码):装饰器模式(不改变原本代码,而增加功能,装饰者接口与被装饰者接口一致)
    1. 装饰者两个缺点:设计上的缺点(注意)
    2. 用户仍然可以创建非计数鸭:抽象工厂(所有的行为要么都被观测,要么不被观察)
      1. 抽象工厂、抽象产品
      2. 具体工厂:计数工厂与不计数工厂
    3. Counting Duck Factory
      1. 产品都是装饰者鸭
  4. 不同的鸭群要分开管理:
    1. 想要在一群鸭子上执行操作:组合模式
    2. 只是监听(中介者模式)
    3. 实现对组合的整体操作,遍历时可以结合迭代器模式
  5. 实时跟踪每一个鸭子的叫的行为:观察者模式
    1. 创建 Observable 类实现 QuackObservable 接口,以组合的方式使得鸭子 Observable
image-20230612124127944 image-20230612124102324

3. MVC

使用模式:策略、组合、观察者

image-20230612153531402

策略模式

视图和控制器实现了经典的策略模式:视图是一个配置了策略的对象。控制器提供策略。视图只关心应用程序的视觉方面,并将有关界面行为的任何决定委托给控制器。使用策略模式还可以使视图与模型分离,因为它是负责与模型交互以执行用户请求的控制器。视图对这是如何完成的一无所知。

观察者模式

模型实现了观察者模式,以便在状态发生变化时保持感兴趣的对象更新。使用观察者模式可以使模型完全独立于视图和控制器。它允许我们对同一个模型使用不同的视图,甚至一次使用多个视图。

组合模式

显示由一组嵌套的窗口、面板、按钮、文本标签等组成。每个显示组件都是一个组合(如窗口)或叶子(如按钮)。当控制器告诉视图更新时,它只需要告诉顶视图组件,Composite 会处理剩下的事情。