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
依赖于 CommandLineTool
和 ClassDiagramGenerator
。
CommandLineTool
依赖于 ClassDiagram
,用于操作和查询类图。
ClassDiagram
与 TypeInfo
、Relationship
等类一起构成类图数据模型。
ClassDiagramGenerator
负责创建 ClassDiagram
及其所有组件。
类设计
类图

设计说明
在迭代三中,我们对系统架构进行了重要扩展,使其能够支持多文件解析、命令行交互和设计模式检测等高级功能。系统中使用了以下设计模式:
-
命令模式(Command Pattern)
- 在
CommandLineTool
类中实现,将用户输入的命令封装成 Command
对象
- 每个命令类都实现了
Command
接口,包含执行逻辑和撤销逻辑
- 使用
Deque<Command>
存储命令历史,支持撤销功能
- 优点:将命令请求与具体操作解耦,支持命令的撤销和重做
-
策略模式(Strategy Pattern)
- 在
ClassDiagram
中实现了多种代码分析策略(类分析、继承分析、循环依赖分析、设计模式分析)
- 每种分析策略作为独立方法,可以通过配置启用或禁用
- 优点:允许动态切换分析策略,便于扩展新的分析方法
-
访问者模式(Visitor Pattern)
- 在
ClassDiagramGenerator
类中,通过 LocalVariableCollector
访问者来收集方法体内的局部变量
- 继承
VoidVisitorAdapter
实现,遍历 AST 节点
- 优点:将算法与对象结构分离,易于添加新的操作
-
组合模式(Composite Pattern)
TypeInfo
类包含 FieldInfo
、MethodInfo
、EnumConstantInfo
等组件
- 类图由多个
TypeInfo
和 Relationship
对象组成
- 优点:统一处理复杂对象层次结构
系统的弹性扩展设计:
-
可配置的分析器
- 通过
loadConfig
方法动态加载 XML 配置文件,启用/禁用不同分析器
- 使用
Set<String>
存储已启用的分析器名称
- 支持自定义分析规则,易于扩展新的分析模块
-
命令行交互的模块化设计
- 清晰的命令处理方法分类(
handleAddCommand
、handleQueryCommand
等)
- 命令解析使用正则表达式,便于扩展新的命令语法
-
关系和异味检测的分离设计
- 关系检测与代码异味检测功能独立,可以单独使用或组合使用
- 设计模式检测独立于代码异味检测,可以通过配置单独启用
-
多文件支持的扩展性
- 使用 Java NIO 的
Files.walkFileTree
递归遍历目录
- 采用访问者模式处理文件,便于扩展对不同类型文件的支持
-
通用数据模型
- 类型和关系的数据模型设计通用,可以支持更多元素类型的扩展
- 使用枚举类型
RelationshipType
和 AccessModifier
,方便添加新的关系类型和访问修饰符
数据结构与算法设计
多文件解析算法
代码实现: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(); return "Undo successful"; }
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) { for (TypeInfo other : types) { if (other.superClass != null && other.superClass.equals(type.name)) return false; } 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; } } boolean hasStaticInstanceField = type.fields.stream() .anyMatch(f -> f.isStatic && f.type.equals(type.name)); 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); }
|
策略模式检测
代码实现: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) { 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、格式汇总。
运行结果
