1.7.3 逻辑视图
根据上下文图,处理过程从训练数据和测试数据开始。也就是将已经正确分类的数据用于测试我们的分类算法。下面是一个包含训练集和测试集的类图,如图1.13所示。
图1.13 用于训练和测试的类图[10]
图中有代表训练集的TrainingData类,它包含name,以及uploaded、tested等时间属性来表示测试集的上传时间、测试完成时间。现在看起来每个TrainingData对象都有一个用于KNN分类器算法的k调节参数。TrainingData也包括两个Sample类型的列表,分别是训练列表和测试列表。
类图中的每个类都用一个包含多个部分的矩形表示:
• 最上面的部分是类的名称。第二个类的名称使用了类型提示(type hint)[11]List[Sample]。list是一个通用类型,里面可以存放任何对象。通过类型提示,我们确保里面只能放Sample对象。
• 下一个部分是对象的各个属性,这些属性也被称为类的实例变量。
• 最后的部分用于添加对象的“方法”,现在都还是空的。
Sample类的每个对象有几个属性:4个浮点数和1个植物学家给这个样本设定的分类。在这里,我们的属性名使用了class,因为class就是分类的意思,请不要与代码的类名混淆。
在UML的箭头中,空心的菱形和实心的菱形代表两种具体的关系。实心菱形表示组合(composition):一个TrainingData对象由两个数据集合组成。空心菱形表示聚合(aggregation):List[Sample]对象是由多个Sample对象聚合而成的。回顾一下我们前面学到的内容:
• 组合是一种共存关系:TrainingData不能没有两个List[Sample]对象。反过来,List[Sample]是为TrainingData而生的,也随着TrainigData的消亡而消亡。没有TrainingData就没有List[Sample]。
• 聚合中的对象是可以各自独立存在的。在图1.13中,多个Sample对象既可以是List[Sample]的一部分,也可以独立于它而存在。
我们不确定通过空心菱形把Sample对象聚合到List对象中是否与当前的讨论相关。这种设计细节也许帮助不大。如果不确定,则最好先去掉这些细节,直到在实现过程中确定需要它们的时候再将它们加回来。
我们用List[Sample]表示一个独立的类。它其实是Python的通用类List,只不过指定了里面存放的对象类型是Sample。通常我们会避免这种细节,它们的关系可以简化为图1.14。
图1.14 浓缩的类图
这个简化的类图对分析工作有帮助,因为使用什么基础数据结构在分析阶段并不重要。但它对设计工作不够友好,因为在设计阶段需要确定具体用什么Python类。
有了这个草图,我们将会把这个逻辑视图和上一节介绍的上下文图(图1.12)中的3个场景一一对比。我们要确保用户故事中的数据和处理任务可以被分配到本图所示各个类的属性和方法中。
看一遍用户故事,我们发现上面的逻辑视图有两个问题:
• 不清楚测试和参数k的对应关系。在图1.14中有一个参数k,但没有表明不同的参数k和相应测试结果之间的关系。[12]
• 在图1.14中根本没有用户请求,也没有请求的结果。没有一个类包含相关的属性或方法。
第一个问题告诉我们,应该重新看一下用户故事并创建一个更好的逻辑视图。第二个问题则是一个边界问题。虽然图中没有网络请求和响应,但更重要的是先描述问题的根本所在——分类和KNN。处理用户请求的网络服务是一种技术解决方案,在刚开始的阶段,我们应该先把它放一下。
现在,我们把注意力转向数据的处理。我们遵循一个似乎比较有效的描述应用程序的顺序。首先描述数据,因为它是最持久的部分,也是通过每次加工、改进始终保留下来的东西。处理过程相对于数据是第二位的,因为它会随着上下文、用户体验和用户偏好的变化而变化。