软统迭代三文档

JClassDiagram 迭代三设计文档(第95组)

团队介绍

姓名 学号 QQ号 职责
卞浩宇 221098106 3255698603 撰写文档
翟志阳 221250161 3056593724 撰写代码
周屿杨 221870148 3591599547 撰写文档
顾益铭 221250142 2408985579 撰写代码

项目目标

在迭代三的开发周期中,我们将重点扩展 JClassDiagram 的系统功能,使其具备多文件解析与处理能力,以有效适配实际 Java 项目开发需求。我们将构建具备参数配置功能的命令行交互界面并集成设计模式识别模块,重点针对单例模式(Singleton Pattern)和策略模式(Strategy Pattern)进行特征提取与模式验证,同时开发支持用户自定义配置的分析器组件,允许开发人员根据具体需求调整模式检测规则。通过本阶段的升级,系统将形成完整的项目级代码分析能力,并建立可扩展的基于类图的程序分析框架。

系统设计

总体设计

项目的主要结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
./src/main/java
├── JClassDiagram.java # 命令行交互式主程序
├── Main.java # 简单使用示例入口
└── command # 命令行工具模块
└── CommandLineTool.java # 命令行工具实现类
└── diagram # 类图生成和分析模块
├── AccessModifier.java # 访问修饰符枚举
├── ClassDiagram.java # 存储类图和提供分析功能
├── ClassDiagramGenerator.java # 解析Java代码生成类图
├── EnumConstantInfo.java # 枚举常量信息类
├── FieldInfo.java # 字段信息类
├── MethodInfo.java # 方法信息类
├── ParameterInfo.java # 参数信息类
├── Relationship.java # 类间关系实体类
├── RelationshipType.java # 关系类型枚举
└── TypeInfo.java # 类型信息类(类/接口/枚举)

职责说明:

  • JClassDiagram: 作为交互式命令行入口,创建 ClassDiagramGenerator 解析 Java 代码,并通过 CommandLineTool 处理用户输入的命令。
  • command 包: 提供命令行交互功能,实现了命令处理、撤销机制和查询功能。
    • CommandLineTool: 负责解析和执行用户输入的命令,如添加/删除类、查询关系等,并支持操作撤销。
  • diagram 包: 核心包,提供类图解析、存储和分析功能。
    • ClassDiagram: 存储类图元素(类型和关系),提供代码异味和设计模式检测功能。
    • ClassDiagramGenerator: 解析 Java 源代码,支持多文件/目录递归解析。
    • TypeInfo/FieldInfo/MethodInfo 等: 定义类图中的基本元素。

模块关系:

  • JClassDiagram 依赖于 CommandLineToolClassDiagramGenerator
  • CommandLineTool 依赖于 ClassDiagram,用于操作和查询类图。
  • ClassDiagramTypeInfoRelationship 等类一起构成类图数据模型。
  • ClassDiagramGenerator 负责创建 ClassDiagram 及其所有组件。

类设计

类图

迭代三类图

设计说明

在迭代三中,我们对系统架构进行了重要扩展,使其能够支持多文件解析、命令行交互和设计模式检测等高级功能。系统中使用了以下设计模式:

  1. 命令模式(Command Pattern)

    • CommandLineTool 类中实现,将用户输入的命令封装成 Command 对象
    • 每个命令类都实现了 Command 接口,包含执行逻辑和撤销逻辑
    • 使用 Deque<Command> 存储命令历史,支持撤销功能
    • 优点:将命令请求与具体操作解耦,支持命令的撤销和重做
  2. 策略模式(Strategy Pattern)

    • ClassDiagram 中实现了多种代码分析策略(类分析、继承分析、循环依赖分析、设计模式分析)
    • 每种分析策略作为独立方法,可以通过配置启用或禁用
    • 优点:允许动态切换分析策略,便于扩展新的分析方法
  3. 访问者模式(Visitor Pattern)

    • ClassDiagramGenerator 类中,通过 LocalVariableCollector 访问者来收集方法体内的局部变量
    • 继承 VoidVisitorAdapter 实现,遍历 AST 节点
    • 优点:将算法与对象结构分离,易于添加新的操作
  4. 组合模式(Composite Pattern)

    • TypeInfo 类包含 FieldInfoMethodInfoEnumConstantInfo 等组件
    • 类图由多个 TypeInfoRelationship 对象组成
    • 优点:统一处理复杂对象层次结构

系统的弹性扩展设计:

  1. 可配置的分析器

    • 通过 loadConfig 方法动态加载 XML 配置文件,启用/禁用不同分析器
    • 使用 Set<String> 存储已启用的分析器名称
    • 支持自定义分析规则,易于扩展新的分析模块
  2. 命令行交互的模块化设计

    • 清晰的命令处理方法分类(handleAddCommandhandleQueryCommand 等)
    • 命令解析使用正则表达式,便于扩展新的命令语法
  3. 关系和异味检测的分离设计

    • 关系检测与代码异味检测功能独立,可以单独使用或组合使用
    • 设计模式检测独立于代码异味检测,可以通过配置单独启用
  4. 多文件支持的扩展性

    • 使用 Java NIO 的 Files.walkFileTree 递归遍历目录
    • 采用访问者模式处理文件,便于扩展对不同类型文件的支持
  5. 通用数据模型

    • 类型和关系的数据模型设计通用,可以支持更多元素类型的扩展
    • 使用枚举类型 RelationshipTypeAccessModifier,方便添加新的关系类型和访问修饰符

数据结构与算法设计

多文件解析算法

代码实现:ClassDiagramGenerator.parse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public ClassDiagram parse(Path sourcePath) throws IOException {
List<TypeInfo> types = new ArrayList<>();
Map<String, Set<String>> associations = new HashMap<>();
Map<String, Set<String>> dependencies = new HashMap<>();

if (Files.isDirectory(sourcePath)) {
// 递归遍历目录
Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".java")) {
processJavaFile(file, types, associations, dependencies); // 解析单个文件
}
return FileVisitResult.CONTINUE;
}
});
} else if (sourcePath.toString().endsWith(".java")) {
processJavaFile(sourcePath, types, associations, dependencies); // 解析单文件
}

// 合并关联和依赖关系
List<Relationship> relationships = new ArrayList<>();
for (Map.Entry<String, Set<String>> entry : associations.entrySet()) {
for (String target : entry.getValue()) {
relationships.add(new Relationship(target, entry.getKey(), RelationshipType.ASSOCIATION));
}
}
for (Map.Entry<String, Set<String>> entry : dependencies.entrySet()) {
for (String target : entry.getValue()) {
relationships.add(new Relationship(target, entry.getKey(), RelationshipType.DEPENDENCY));
}
}
return new ClassDiagram(types, relationships);
}

关键点

  • 递归目录遍历:通过 Files.walkFileTree 实现多文件解析。

  • 泛型类型提取extractCustomTypes() 方法处理复杂类型(如 Map<String, List<Entity>>):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private Set<String> extractCustomTypes(String typeStr) {
    Set<String> customTypes = new HashSet<>();
    // 正则表达式提取主类型和嵌套泛型
    Pattern pattern = Pattern.compile("([A-Za-z0-9_]+)(?:<.*?>)?");
    Matcher matcher = pattern.matcher(typeStr.replace("[]", ""));
    while (matcher.find()) {
    String type = matcher.group(1);
    if (!BASIC_TYPES.contains(type) && !CONTAINER_TYPES.contains(type)) {
    customTypes.add(type);
    }
    }
    return customTypes;
    }

命令模式与撤销机制

代码实现:CommandLineTool.execute()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public String execute(String commandStr) {
// 命令解析
String[] parts = commandStr.split("\\s+");
String operation = parts[0].toLowerCase();
return switch (operation) {
case "add" -> handleAddCommand(commandStr);
case "delete" -> handleDeleteCommand(commandStr);
case "undo" -> handleUndoCommand();
// ... 其他命令处理
};
}

// 添加类命令示例
private String addClassOrInterface(String commandStr, boolean isInterface) {
Pattern pattern = Pattern.compile(isInterface ? "-i\\s+(\\S+)" : "-c\\s+(\\S+)");
Matcher matcher = pattern.matcher(commandStr);
if (matcher.find()) {
String name = matcher.group(1);
TypeInfo newType = new TypeInfo(name, isInterface, commandStr.contains("--abstract"));
// 保存操作前的状态以实现撤销
commandHistory.push(new AddTypeCommand(diagram, newType, new ArrayList<>(diagram.getTypes())));
diagram.getTypes().add(newType);
return "Class added: " + name;
}
return "Invalid command format";
}

// 撤销操作实现
private String handleUndoCommand() {
if (commandHistory.isEmpty()) return "No command to undo";
Command lastCommand = commandHistory.pop();
lastCommand.undo(); // 调用具体Command的撤销逻辑
return "Undo successful";
}

// Command接口定义
private interface Command {
void undo();
}

// 添加类命令的具体实现
private static class AddTypeCommand implements Command {
private final ClassDiagram diagram;
private final List<TypeInfo> previousTypes;

public AddTypeCommand(ClassDiagram diagram, TypeInfo addedType, List<TypeInfo> previousTypes) {
this.diagram = diagram;
this.previousTypes = previousTypes; // 保存操作前的类型列表
}

@Override
public void undo() {
diagram.setTypes(previousTypes); // 恢复到操作前的状态
}
}

关键点

  • 命令封装:每个操作封装为 Command 对象,保存操作前的状态。
  • 撤销栈:通过 Deque<Command> 实现操作历史管理。

单例模式检查

代码实现:ClassDiagram.isSingletonPattern()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private boolean isSingletonPattern(TypeInfo type) {
// 条件1:无子类
for (TypeInfo other : types) {
if (other.superClass != null && other.superClass.equals(type.name)) return false;
}
// 条件2:存在私有构造函数且无公共构造函数
boolean hasPrivateConstructor = false, hasPublicConstructor = false;
for (MethodInfo method : type.methods) {
if (method.name.equals(getBaseName(type.name))) { // 构造函数
if (method.access == AccessModifier.PRIVATE) hasPrivateConstructor = true;
else if (method.access == AccessModifier.PUBLIC) hasPublicConstructor = true;
}
}
// 条件3:静态私有字段
boolean hasStaticInstanceField = type.fields.stream()
.anyMatch(f -> f.isStatic && f.type.equals(type.name));
// 条件4:静态公有获取方法
boolean hasStaticGetter = type.methods.stream()
.anyMatch(m -> m.isStatic && m.access == AccessModifier.PUBLIC && m.returnType.equals(type.name));

return hasPrivateConstructor && !hasPublicConstructor && hasStaticInstanceField && hasStaticGetter;
}

触发条件:在getCodeSmells()中调用:

1
2
3
if (enabledAnalyzers.contains("DesignPatternAnalyzer")) {
findDesignPatterns(smells); // 内部调用 isSingletonPattern()
}

策略模式检测

代码实现:ClassDiagram.hasStrategyPattern()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private boolean hasStrategyPattern() {
for (TypeInfo strategyType : types) {
// 策略接口需为接口或抽象类,且名称以 Strategy/Policy/Behavior 结尾
if (!isStrategyType(strategyType)) continue;

// 统计具体策略类数量
List<TypeInfo> implementations = types.stream()
.filter(t -> t.implementedInterfaces.contains(strategyType.name) || t.superClass.equals(strategyType.name))
.toList();
if (implementations.size() < 2) continue;

// 检查上下文类的关联关系
boolean hasContextClass = relationships.stream()
.anyMatch(rel -> rel.type == RelationshipType.ASSOCIATION &&
(rel.source.equals(strategyType.name) || rel.target.equals(strategyType.name)));

if (hasContextClass) return true;
}
return false;
}

private boolean isStrategyType(TypeInfo type) {
return (type.isInterface || type.isAbstract) &&
!type.methods.isEmpty() &&
(type.name.endsWith("Strategy") || type.name.endsWith("Policy") || type.name.endsWith("Behavior"));
}

可适配分配器

代码实现:ClassDiagram.loadConfig()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void loadConfig(String configFile) {
try {
enabledAnalyzers.clear();
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(configFile));
NodeList nodes = doc.getElementsByTagName("analyzer");
for (int i = 0; i < nodes.getLength(); i++) {
String analyzerName = nodes.item(i).getTextContent().trim();
enabledAnalyzers.add(analyzerName); // 仅启用配置中的分析器
}
} catch (Exception e) {
// 出错时回退到默认配置
enabledAnalyzers.addAll(Arrays.asList("ClassAnalyzer", "InheritanceTreeAnalyzer", ...));
}
}

通过上述代码实现,系统实现了以下核心功能:

  • 多文件解析:递归遍历目录,合并类图关系。

  • 命令模式:通过 Command 接口和 Deque 实现操作撤销。

  • 设计模式检测:单例模式通过字段和构造函数验证,策略模式通过接口关联统计。

  • 可配置分析器:动态启用/禁用分析模块,提升灵活性。

每个功能模块均通过高内聚的代码实现,确保系统的可扩展性和维护性。

附录

实际工作安排

  • 卞浩宇:文档撰写,总体设计部分、类设计部分与绘图、运行结果、格式汇总。
  • 翟志阳:Java 代码撰写,Part 3。
  • 周屿杨:文档撰写,数据结构与算法设计部分。
  • 顾益铭:Java 代码撰写,Part 1 和 Part 2、格式汇总。

运行结果

运行结果展示


软统迭代三文档
https://lngym.top/课程作业/软统迭代三文档/
作者
lngym
发布于
2025年4月30日
许可协议