diff --git a/out/production/TestPaperGenerationSystem/AbstractProblemGenerator.class b/out/production/TestPaperGenerationSystem/AbstractProblemGenerator.class index c250617..d2cb979 100644 Binary files a/out/production/TestPaperGenerationSystem/AbstractProblemGenerator.class and b/out/production/TestPaperGenerationSystem/AbstractProblemGenerator.class differ diff --git a/out/production/TestPaperGenerationSystem/Application.class b/out/production/TestPaperGenerationSystem/Application.class index ed2fc01..4aa1052 100644 Binary files a/out/production/TestPaperGenerationSystem/Application.class and b/out/production/TestPaperGenerationSystem/Application.class differ diff --git a/out/production/TestPaperGenerationSystem/AuthService.class b/out/production/TestPaperGenerationSystem/AuthService.class index 4880a95..bfa23c7 100644 Binary files a/out/production/TestPaperGenerationSystem/AuthService.class and b/out/production/TestPaperGenerationSystem/AuthService.class differ diff --git a/out/production/TestPaperGenerationSystem/EducationLevel.class b/out/production/TestPaperGenerationSystem/EducationLevel.class deleted file mode 100644 index 248f809..0000000 Binary files a/out/production/TestPaperGenerationSystem/EducationLevel.class and /dev/null differ diff --git a/out/production/TestPaperGenerationSystem/ElementaryProblemGenerator.class b/out/production/TestPaperGenerationSystem/ElementaryProblemGenerator.class index 0fbbb59..ae02b8b 100644 Binary files a/out/production/TestPaperGenerationSystem/ElementaryProblemGenerator.class and b/out/production/TestPaperGenerationSystem/ElementaryProblemGenerator.class differ diff --git a/out/production/TestPaperGenerationSystem/Equation.class b/out/production/TestPaperGenerationSystem/Equation.class index c2901c2..158f21c 100644 Binary files a/out/production/TestPaperGenerationSystem/Equation.class and b/out/production/TestPaperGenerationSystem/Equation.class differ diff --git a/out/production/TestPaperGenerationSystem/HighSchoolProblemGenerator.class b/out/production/TestPaperGenerationSystem/HighSchoolProblemGenerator.class index 28c410e..75998fb 100644 Binary files a/out/production/TestPaperGenerationSystem/HighSchoolProblemGenerator.class and b/out/production/TestPaperGenerationSystem/HighSchoolProblemGenerator.class differ diff --git a/out/production/TestPaperGenerationSystem/InMemoryUserRepository.class b/out/production/TestPaperGenerationSystem/InMemoryUserRepository.class deleted file mode 100644 index e8cd1e9..0000000 Binary files a/out/production/TestPaperGenerationSystem/InMemoryUserRepository.class and /dev/null differ diff --git a/out/production/TestPaperGenerationSystem/InvalidInputException.class b/out/production/TestPaperGenerationSystem/InvalidInputException.class deleted file mode 100644 index a9ea193..0000000 Binary files a/out/production/TestPaperGenerationSystem/InvalidInputException.class and /dev/null differ diff --git a/out/production/TestPaperGenerationSystem/MiddleSchoolProblemGenerator.class b/out/production/TestPaperGenerationSystem/MiddleSchoolProblemGenerator.class index 78a2dd4..3ac3c68 100644 Binary files a/out/production/TestPaperGenerationSystem/MiddleSchoolProblemGenerator.class and b/out/production/TestPaperGenerationSystem/MiddleSchoolProblemGenerator.class differ diff --git a/out/production/TestPaperGenerationSystem/Operator.class b/out/production/TestPaperGenerationSystem/Operator.class index ac39f88..5de7fbf 100644 Binary files a/out/production/TestPaperGenerationSystem/Operator.class and b/out/production/TestPaperGenerationSystem/Operator.class differ diff --git a/out/production/TestPaperGenerationSystem/SessionManager$1.class b/out/production/TestPaperGenerationSystem/SessionManager$1.class deleted file mode 100644 index fee6f24..0000000 Binary files a/out/production/TestPaperGenerationSystem/SessionManager$1.class and /dev/null differ diff --git a/out/production/TestPaperGenerationSystem/SessionManager.class b/out/production/TestPaperGenerationSystem/SessionManager.class index 7637efd..67f6946 100644 Binary files a/out/production/TestPaperGenerationSystem/SessionManager.class and b/out/production/TestPaperGenerationSystem/SessionManager.class differ diff --git a/out/production/TestPaperGenerationSystem/TextFilePersistence.class b/out/production/TestPaperGenerationSystem/TextFilePersistence.class deleted file mode 100644 index 2fdc1b1..0000000 Binary files a/out/production/TestPaperGenerationSystem/TextFilePersistence.class and /dev/null differ diff --git a/out/production/TestPaperGenerationSystem/User.class b/out/production/TestPaperGenerationSystem/User.class index 5dc04d7..c5509f7 100644 Binary files a/out/production/TestPaperGenerationSystem/User.class and b/out/production/TestPaperGenerationSystem/User.class differ diff --git a/out/production/TestPaperGenerationSystem/UserRepository.class b/out/production/TestPaperGenerationSystem/UserRepository.class index a296ed8..621f0bb 100644 Binary files a/out/production/TestPaperGenerationSystem/UserRepository.class and b/out/production/TestPaperGenerationSystem/UserRepository.class differ diff --git a/src/AbstractProblemGenerator.java b/src/AbstractProblemGenerator.java index 4c84464..d33074a 100644 --- a/src/AbstractProblemGenerator.java +++ b/src/AbstractProblemGenerator.java @@ -1,13 +1,15 @@ import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 抽象题目生成器(模板方法模式)。 - * 实现了 IProblemGenerator 接口,提供了通用的题目生成逻辑, - * 如唯一性检查循环、操作数生成等,将具体的题目构建逻辑延迟到子类。 + * 提供了生成题目的通用流程和创建基础四则运算的核心方法。 */ public abstract class AbstractProblemGenerator implements IProblemGenerator { @@ -17,51 +19,153 @@ public abstract class AbstractProblemGenerator implements IProblemGenerator { protected static final int MIN_OPERANDS_COUNT = 2; protected static final int MAX_OPERANDS_COUNT = 5; + private static final Operator[] AVAILABLE_OPERATORS = { + Operator.ADD, Operator.SUBTRACT, Operator.MULTIPLY, Operator.DIVIDE + }; + @Override - public List generate(int count, Set existingProblems) { + public final List generate(int count, Set existingProblems) { List newProblems = new ArrayList<>(count); - // 使用一个临时的Set来防止在同一次生成任务中产生重复 Set currentBatchSet = new HashSet<>(); - while (newProblems.size() < count) { - Equation candidate = createProblem(); + int maxTries = count * 100; + int tries = 0; - String canonicalForm = candidate.toCanonicalString(); + while (newProblems.size() < count && tries < maxTries) { + Equation candidate = createProblem(); + String canonicalForm = candidate.toString(); - // 执行唯一性检查:既不能与历史重复,也不能与本次生成的其他题目重复 - if (!existingProblems.contains(canonicalForm) && !currentBatchSet.contains(canonicalForm)) { + if (!existingProblems.contains(canonicalForm) && currentBatchSet.add(canonicalForm)) { newProblems.add(candidate); - currentBatchSet.add(canonicalForm); } + tries++; } return newProblems; } /** - * 创建一个候选数学题目的抽象方法(模板方法)。 - * 子类必须实现此方法来定义具体难度的题目生成规则。 - * - * @return 一个新创建的 Equation 对象 + * 模板方法:创建一道具体问题的抽象定义,由子类实现。 */ protected abstract Equation createProblem(); /** - * 生成一个在指定范围内的随机整数。 + * 创建一个基础的四则运算方程式。 + * 此方法已被重构,将复杂逻辑委托给私有辅助方法。 * - * @param min 最小值(包含) - * @param max 最大值(包含) - * @return 随机整数 + * @param nonNegativeOnly 如果为 true,则强制保证所有中间和最终结果非负。 + * @return 一个 Equation 对象。 */ - protected int getRandomNumber(int min, int max) { - return random.nextInt(max - min + 1) + min; + protected Equation createBaseArithmeticProblem(boolean nonNegativeOnly) { + int numOperands = getRandomNumber(MIN_OPERANDS_COUNT, MAX_OPERANDS_COUNT); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + int currentResult = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE); + operands.add(String.valueOf(currentResult)); + + for (int i = 0; i < numOperands - 1; i++) { + if (nonNegativeOnly) { + currentResult = appendNonNegativeOperation(operands, operators, currentResult); + } else { + appendStandardOperation(operands, operators); + } + } + + addParentheses(operands, operators); + return new Equation(operands, operators); + } + + /** + * (Refactored Helper) 追加一个确保结果非负的运算步骤。 + * + * @param operands 当前操作数列表 + * @param operators 当前操作符列表 + * @param currentResult 当前的累计计算结果 + * @return 更新后的累计计算结果 + */ + private int appendNonNegativeOperation(List operands, List operators, int currentResult) { + Operator operator = AVAILABLE_OPERATORS[random.nextInt(AVAILABLE_OPERATORS.length)]; + int nextOperand; + int newResult = currentResult; + + switch (operator) { + case ADD: + nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE); + newResult += nextOperand; + break; + case SUBTRACT: + nextOperand = getRandomNumber(MIN_OPERAND_VALUE, currentResult); + newResult -= nextOperand; + break; + case MULTIPLY: + nextOperand = getRandomNumber(MIN_OPERAND_VALUE, 10); + newResult *= nextOperand; + break; + case DIVIDE: + List divisors = findDivisors(currentResult); + nextOperand = divisors.get(random.nextInt(divisors.size())); + newResult /= nextOperand; + break; + default: + // 为了编译安全,实际上不会执行到这里 + throw new IllegalStateException("Unexpected value: " + operator); + } + operands.add(String.valueOf(nextOperand)); + operators.add(operator); + return newResult; } /** - * 从给定的操作符数组中随机选择一个。 - * @param availableOperators 可用的操作符数组 - * @return 随机选择的操作符 + * (Refactored Helper) 追加一个标准的运算步骤(允许负数)。 + * + * @param operands 当前操作数列表 + * @param operators 当前操作符列表 */ - protected Operator getRandomOperator(Operator[] availableOperators) { - return availableOperators[random.nextInt(availableOperators.length)]; + private void appendStandardOperation(List operands, List operators) { + Operator operator = AVAILABLE_OPERATORS[random.nextInt(AVAILABLE_OPERATORS.length)]; + int nextOperand; + + if (operator == Operator.DIVIDE) { + // 为确保整除,反向构造 + int divisor = getRandomNumber(1, 10); + int quotient = getRandomNumber(1, 20); + // 替换前一个操作数以确保可整除 + operands.set(operands.size() - 1, String.valueOf(divisor * quotient)); + nextOperand = divisor; + } else { + nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE); + } + operands.add(String.valueOf(nextOperand)); + operators.add(operator); + } + + + private void addParentheses(List operands, List operators) { + if (operators.size() > 1 && random.nextBoolean()) { + int pos = random.nextInt(operators.size()); + Operator op = operators.get(pos); + // 仅在优先级较低的运算符旁边添加括号才有视觉意义 + if (op == Operator.ADD || op == Operator.SUBTRACT) { + operands.set(pos, "(" + operands.get(pos)); + operands.set(pos + 1, operands.get(pos + 1) + ")"); + } + } + } + + private List findDivisors(int number) { + if (number == 0) { + return Collections.singletonList(1); // 避免除以0 + } + return IntStream.rangeClosed(1, Math.abs(number)) + .filter(i -> number % i == 0) + .boxed() + .collect(Collectors.toList()); + } + + protected int getRandomNumber(int min, int max) { + if (min > max) { + return max; // 安全处理,例如当currentResult变为1时 + } + return random.nextInt(max - min + 1) + min; } } \ No newline at end of file diff --git a/src/AbstractSessionManager.java b/src/AbstractSessionManager.java new file mode 100644 index 0000000..e5fb5be --- /dev/null +++ b/src/AbstractSessionManager.java @@ -0,0 +1,66 @@ +/** + * 抽象会话管理器。 + * 定义了会话管理器的基本框架,包含核心的属性和必须实现的功能。 + * 子类需要提供具体的实现,例如单例模式的实现和题目生成器的实例化逻辑。 + */ +public abstract class AbstractSessionManager { + + protected User currentUser; + protected IProblemGenerator currentGenerator; + protected String currentLevel; + + /** + * 在用户成功登录后启动一个会话。 + * + * @param user 登录成功的用户对象 + */ + public abstract void startSession(User user); + + /** + * 结束当前会话(用户退出登录)。 + */ + public abstract void endSession(); + + /** + * 根据用户的选择,动态更改当前的题目生成策略。 + * + * @param level 新的教育级别 + */ + public abstract void setCurrentGenerator(String level); + + /** + * 检查用户是否已登录。 + * + * @return 如果用户已登录,则返回 true,否则返回 false + */ + public boolean isUserLoggedIn() { + return currentUser != null; + } + + /** + * 获取当前登录的用户。 + * + * @return 当前用户对象 + */ + public User getCurrentUser() { + return currentUser; + } + + /** + * 获取当前的题目生成器。 + * + * @return 当前题目生成器实例 + */ + public IProblemGenerator getCurrentGenerator() { + return currentGenerator; + } + + /** + * 获取当前选定级别的中文名称。 + * + * @return 中文级别名称,如 "小学" + */ + public String getCurrentLevelName() { + return currentLevel; + } +} \ No newline at end of file diff --git a/src/AbstractUser.java b/src/AbstractUser.java new file mode 100644 index 0000000..8e022db --- /dev/null +++ b/src/AbstractUser.java @@ -0,0 +1,61 @@ +import java.util.Objects; + +/** + * 抽象用户类。 + * 封装了所有用户类型共有的核心属性和行为,例如用户名和密码。 + */ +public abstract class AbstractUser { + + protected final String username; + protected final String password; + + /** + * 构造一个抽象用户。 + * + * @param username 用户名 + * @param password 密码 + */ + public AbstractUser(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + /** + * 比较此用户与指定对象是否相等。 + * 如果两个用户的用户名相同,则认为他们相等。 + * + * @param o 要与之比较的对象 + * @return 如果对象是同一个用户,则返回 true;否则返回 false + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + // 使用 getClass() != o.getClass() 来确保子类之间不会错误地相等 + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractUser that = (AbstractUser) o; + return username.equals(that.username); + } + + /** + * 返回用户的哈希码。 + * 哈希码是根据用户名生成的。 + * + * @return 此对象的哈希码值 + */ + @Override + public int hashCode() { + return Objects.hash(username); + } +} \ No newline at end of file diff --git a/src/Application.java b/src/Application.java index 0791925..2642a0a 100644 --- a/src/Application.java +++ b/src/Application.java @@ -20,16 +20,13 @@ public class Application { // 用于解析“切换为XX”命令的正则表达式 private static final Pattern SWITCH_COMMAND_PATTERN = Pattern.compile("^切换为(小学|初中|高中)$"); - private static final int MIN_GENERATE_COUNT = 10; - private static final int MAX_GENERATE_COUNT = 30; public Application() { this.scanner = new Scanner(System.in, "UTF-8"); - // --- 依赖注入与组件装配 --- // 创建数据访问层的实例 - UserRepository userRepository = new InMemoryUserRepository(); - this.fileService = new TextFilePersistence(); + IUserRepository userRepository = new UserRepository(); + this.fileService = new FileService(); // 创建应用服务层的实例,并注入依赖 this.authService = new AuthService(userRepository); // 获取会话管理器的单例实例 @@ -39,7 +36,7 @@ public class Application { /** * 启动并运行应用程序的主循环。 */ - public void run() { + public void run() throws IOException { System.out.println("欢迎使用中小学数学卷子自动生成程序!"); while (true) { if (!sessionManager.isUserLoggedIn()) { @@ -62,7 +59,7 @@ public class Application { System.exit(0); } - String[] credentials = input.split("\\s+"); + String[] credentials = input.split(" "); if (credentials.length != 2) { System.out.println("输入格式错误,请输入正确的用户名、密码。"); return; @@ -71,7 +68,7 @@ public class Application { Optional userOptional = authService.login(credentials[0], credentials[1]); if (userOptional.isPresent()) { sessionManager.startSession(userOptional.get()); - System.out.println("登录成功!欢迎您," + userOptional.get().getUsername() + "。"); + System.out.println("登录成功!欢迎您," + credentials[0] + "。"); } else { System.out.println("用户名或密码错误,请重试。"); } @@ -80,30 +77,23 @@ public class Application { /** * 处理用户已登录状态下的逻辑。 */ - private void handleLoggedInState() { - String currentLevelName = sessionManager.getCurrentLevelName(); + private void handleLoggedInState() throws IOException { + String currentLevel = sessionManager.getCurrentLevel(); System.out.printf( - "当前选择为%s出题。请输入生成题目数量(%d-%d),或输入 '切换为XX',或输入 '-1' 退出登录:%n", - currentLevelName, MIN_GENERATE_COUNT, MAX_GENERATE_COUNT); + "当前选择为%s出题。请输入生成题目数量(10-30),或输入 '切换为XX',或输入 '-1' 退出登录:%n", + currentLevel); String input = scanner.nextLine().trim(); - try { if ("-1".equals(input)) { handleLogout(); } else if (isSwitchCommand(input)) { handleSwitchLevel(input); } else if (isNumeric(input)) { - int count = Integer.parseInt(input); - validateGenerateCount(count); - handleGenerateProblems(count); + handleGenerateProblems(input); } else { - throw new InvalidInputException( - String.format("请输入小学、初中和高中三个选项中的一个", - MIN_GENERATE_COUNT, MAX_GENERATE_COUNT)); + System.out.println("无效的选项,请重新输入"); } - } catch (InvalidInputException | IOException | NumberFormatException e) { - System.err.println("错误: " + e.getMessage()); - } + } private void handleLogout() { @@ -111,22 +101,25 @@ public class Application { sessionManager.endSession(); } - private void handleSwitchLevel(String input) throws InvalidInputException { + private void handleSwitchLevel(String input) { Matcher matcher = SWITCH_COMMAND_PATTERN.matcher(input); if (matcher.matches()) { - String levelName = matcher.group(1); - EducationLevel level = EducationLevel.fromLevelName(levelName); + String level = matcher.group(1); if (level != null) { sessionManager.setCurrentGenerator(level); - System.out.println("成功切换到 " + levelName + " 出题模式。"); - } else { - // 这部分代码理论上不会执行,因为正则表达式已经限制了级别名称 - throw new InvalidInputException("无效的切换目标级别。"); + System.out.println("成功切换到 " + level + " 出题模式。"); } } } - private void handleGenerateProblems(int count) throws IOException { + private void handleGenerateProblems(String input) throws IOException { + + int count = Integer.parseInt(input); + if (count < 10 || count > 30) { + System.out.println("生成数量必须在10到30之间。"); + return; + } + User currentUser = sessionManager.getCurrentUser(); System.out.println("正在加载历史题目,请稍候..."); Set history = fileService.loadAllProblemHistory(currentUser); @@ -135,11 +128,11 @@ public class Application { System.out.println("正在生成 " + count + " 道不重复的新题目..."); IProblemGenerator generator = sessionManager.getCurrentGenerator(); List newProblems = generator.generate(count, history); - //命令行显示生成题目 + + // 命令行显示生成题目 for (int i = 0; i < newProblems.size(); i++) { String problemLine = (i + 1) + ". " + newProblems.get(i).toString(); System.out.println(problemLine); - // 满足题目之间空一行的格式要求 System.out.println(); } @@ -153,17 +146,10 @@ public class Application { } private boolean isNumeric(String str) { - return str != null && str.matches("-?\\d+"); - } - - private void validateGenerateCount(int count) throws InvalidInputException { - if (count < MIN_GENERATE_COUNT || count > MAX_GENERATE_COUNT) { - throw new InvalidInputException( - String.format("生成数量必须在 %d 到 %d 之间。", MIN_GENERATE_COUNT, MAX_GENERATE_COUNT)); - } + return str != null && str.matches("\\d+"); } - public static void main(String[] args) { + public static void main(String[] args) throws IOException { Application app = new Application(); app.run(); } diff --git a/src/AuthService.java b/src/AuthService.java index 9e358a2..e9038df 100644 --- a/src/AuthService.java +++ b/src/AuthService.java @@ -4,11 +4,11 @@ import java.util.Optional; * 用户认证服务。 * 这是一个无状态的服务类,专门负责处理用户身份验证的逻辑。 */ -public class AuthService { +public class AuthService implements IAuthService { - private final UserRepository userRepository; + private final IUserRepository userRepository; - public AuthService(UserRepository userRepository) { + public AuthService(IUserRepository userRepository) { this.userRepository = userRepository; } @@ -17,7 +17,7 @@ public class AuthService { * * @param username 用户名 * @param password 密码 - * @return 如果认证成功,返回对应的 User 对象;否则返回一个空的 Optional + * @return 如果认证成功,返回ture,否则返回false */ public Optional login(String username, String password) { Optional userOptional = userRepository.findByUsername(username); diff --git a/src/EducationLevel.java b/src/EducationLevel.java deleted file mode 100644 index f345a7a..0000000 --- a/src/EducationLevel.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 教育级别枚举。 - * 用于表示用户的默认级别以及题目生成的难度。 - */ -public enum EducationLevel { - PRIMARY("小学"), - MIDDLE("初中"), - HIGH("高中"); - - private final String levelName; - - EducationLevel(String levelName) { - this.levelName = levelName; - } - - /** - * 获取级别的名称。 - * - * @return 级别名称字符串 - */ - public String getLevelName() { - return levelName; - } - - /** - * 根据中称查找对应的枚举实例。 - * - * @param levelName 要查找的名称,例如 "初中" - * @return 匹配的 EducationLevel 实例,若未找到则返回 null - */ - public static EducationLevel fromLevelName(String levelName) { - for (EducationLevel level : values()) { - if (level.getLevelName().equals(levelName)) { - return level; - } - } - return null; - } -} \ No newline at end of file diff --git a/src/ElementaryProblemGenerator.java b/src/ElementaryProblemGenerator.java index 50cef20..26e8a0b 100644 --- a/src/ElementaryProblemGenerator.java +++ b/src/ElementaryProblemGenerator.java @@ -1,45 +1,12 @@ -import java.util.ArrayList; -import java.util.List; - /** * 小学题目生成器(具体策略)。 - * 生成包含2-5个操作数的四则运算题目。 + * 其实现委托给基类的核心方法,并强制要求结果非负。 */ public class ElementaryProblemGenerator extends AbstractProblemGenerator { - private static final Operator[] AVAILABLE_OPERATORS = { - Operator.ADD, Operator.SUBTRACT, Operator.MULTIPLY, Operator.DIVIDE - }; - @Override protected Equation createProblem() { - int numOperands = getRandomNumber(MIN_OPERANDS_COUNT, MAX_OPERANDS_COUNT); - List operands = new ArrayList<>(); - List operators = new ArrayList<>(); - - // 生成第一个操作数 - operands.add(getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE)); - - for (int i = 0; i < numOperands - 1; i++) { - Operator operator = getRandomOperator(AVAILABLE_OPERATORS); - int nextOperand; - - // 特殊处理除法,确保除数不为0且能整除,避免产生小数 - if (operator == Operator.DIVIDE) { - // 为了简化,我们让被除数是新生成的随机数的倍数 - int divisor = getRandomNumber(1, 10); // 将除数范围缩小以增加整除概率 - int quotient = getRandomNumber(1, 10); - int dividend = operands.get(operands.size() - 1); // 获取前一个结果或操作数 - operands.set(operands.size() - 1, divisor * quotient); // 替换前一个操作数确保能整除 - nextOperand = divisor; - } else { - nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE); - } - - operands.add(nextOperand); - operators.add(operator); - } - - return new Equation(operands, operators, true); + // 调用父类的核心方法,并传入 true,要求启用“无负数”约束。 + return createBaseArithmeticProblem(true); } } \ No newline at end of file diff --git a/src/Equation.java b/src/Equation.java index 3bf80a9..8899a51 100644 --- a/src/Equation.java +++ b/src/Equation.java @@ -1,77 +1,65 @@ -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 方程式实体类。 - * 封装了构成一个数学方程式的所有元素(操作数、操作符)及其行为(计算、格式化输出)。 + * 封装了构成一个数学方程式的所有元素(操作数、操作符)。 + * 操作数统一使用 String 类型,以支持数字、函数表达式等多种形式。 */ public class Equation { - private final List operands; // 使用String以支持如 "sqrt(16)" 的形式 + private final List operands; private final List operators; + /** + * 构造函数。 + * + * @param operands 操作数列表(可以是数字、函数表达式等) + * @param operators 操作符列表 + */ public Equation(List operands, List operators) { - this.operands = new ArrayList<>(operands); - this.operators = new ArrayList<>(operators); - } - - // 为了方便,提供一个只接受整数操作数的构造函数 - public Equation(List intOperands, List operators, boolean isSimple) { - this.operands = intOperands.stream().map(String::valueOf).collect(Collectors.toList()); - this.operators = new ArrayList<>(operators); + this.operands = Collections.unmodifiableList(operands); + this.operators = Collections.unmodifiableList(operators); } + /** + * 获取操作数列表的不可变视图。 + * + * @return 操作数列表 + */ public List getOperands() { - return Collections.unmodifiableList(operands); + return operands; } + /** + * 获取操作符列表的不可变视图。 + * + * @return 操作符列表 + */ public List getOperators() { - return Collections.unmodifiableList(operators); + return operators; } /** - * 生成用于展示给用户和保存到文件的标准字符串。 - * 格式: "操作数1 运算符1 操作数2 = 结果" + * 生成用于展示给用户和保存到文件的标准问题字符串。 + * 格式: "操作数1 运算符1 操作数2 = ?" * * @return 格式化的方程式字符串 */ @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(operands.get(0)); - for (int i = 0; i < operators.size(); i++) { - sb.append(" ").append(operators.get(i).getSymbol()).append(" "); - sb.append(operands.get(i + 1)); - } - sb.append(" = ").append("?"); + // 使用 IntStream 处理操作数和操作符的交替拼接 + IntStream.range(0, operators.size()) + .forEach(i -> sb.append(operands.get(i)) + .append(" ") + .append(operators.get(i).getSymbol()) + .append(" ")); + sb.append(operands.get(operands.size() - 1)); // 追加最后一个操作数 + sb.append(" = ?"); return sb.toString(); } - /** - * 生成用于唯一性校验的“规范化”字符串。 - * 这是确保 "3 + 5" 和 "5 + 3" 被视为同一题目的关键。 - * 规范化规则:对于可交换运算符,操作数按升序排列。 - * - * @return 规范化的方程式字符串 - */ - public String toCanonicalString() { - if (operators.size() == 1 && operators.get(0).isCommutative()) { - // 仅处理最简单的二元运算场景 - String[] sortedOperands = operands.toArray(new String[0]); - Arrays.sort(sortedOperands); - return sortedOperands[0] + " " + operators.get(0).getSymbol() + " " + sortedOperands[1]; - } - - // 对于更复杂的或不可交换的运算,直接按顺序拼接 - StringBuilder sb = new StringBuilder(); - sb.append(operands.get(0)); - for (int i = 0; i < operators.size(); i++) { - sb.append(" ").append(operators.get(i).getSymbol()).append(" "); - sb.append(operands.get(i + 1)); - } - return sb.toString(); - } } \ No newline at end of file diff --git a/src/TextFilePersistence.java b/src/FileService.java similarity index 60% rename from src/TextFilePersistence.java rename to src/FileService.java index c93a69c..352aeeb 100644 --- a/src/TextFilePersistence.java +++ b/src/FileService.java @@ -12,21 +12,16 @@ import java.time.format.DateTimeFormatter; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * 文件服务的文本文件实现。 * 负责将题目数据以文本格式读写到本地文件系统。 */ -public class TextFilePersistence implements IFileService { +public class FileService implements IFileService { private static final DateTimeFormatter FILE_NAME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"); - // 正则表达式用于从文件中解析出题目表达式部分(忽略题号和答案) - private static final Pattern PROBLEM_PATTERN = Pattern.compile("^\\d+\\.\\s*(.+?)\\s*="); - @Override public void saveProblems(User user, List problems) throws IOException { Path userDirectory = Paths.get(user.getStoragePath()); @@ -71,39 +66,15 @@ public class TextFilePersistence implements IFileService { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line; while ((line = reader.readLine()) != null) { - Matcher matcher = PROBLEM_PATTERN.matcher(line.trim()); - if (matcher.find()) { - String expression = matcher.group(1).trim(); - // 将解析出的表达式转换为规范化形式 - // 这是一个简化的转换,假设表达式格式固定 - // 更健壮的实现会构建一个临时的 Equation 对象来获取其规范化字符串 - String canonicalForm = normalizeExpression(expression); - historySet.add(canonicalForm); + if (!line.trim().isEmpty()) { + String cleanedLine = line.replaceFirst("^\\d+\\.\\s*", ""); + historySet.add(cleanedLine); } } } } + return historySet; } - /** - * 一个简化的字符串层面规范化方法。 - * 它模拟了 Equation.toCanonicalString() 的逻辑。 - * - * @param expression 从文件中读取的表达式,例如 "5 + 3" 或 "12 * 4" - * @return 规范化后的字符串 - */ - private String normalizeExpression(String expression) { - String[] parts = expression.split(" "); - if (parts.length == 3) { - String op = parts[1]; - if (op.equals("+") || op.equals("*")) { - // 如果是可交换运算符,对操作数进行排序 - if (parts[0].compareTo(parts[2]) > 0) { - return parts[2] + " " + op + " " + parts[0]; - } - } - } - return expression; // 其他情况直接返回 - } } \ No newline at end of file diff --git a/src/HighSchoolProblemGenerator.java b/src/HighSchoolProblemGenerator.java index 22739ba..ff3bdcc 100644 --- a/src/HighSchoolProblemGenerator.java +++ b/src/HighSchoolProblemGenerator.java @@ -3,52 +3,34 @@ import java.util.List; /** * 高中题目生成器(具体策略)。 - * 作为初中生成器的子类,它首先生成一个包含初中难度运算(平方/开方)的题目, + * 它首先生成一个包含初中难度运算(平方/开方)的题目, * 然后再额外加入一个三角函数运算,使得题目难度递增。 */ public class HighSchoolProblemGenerator extends MiddleSchoolProblemGenerator { - // 预定义一些常见的三角函数角度 private static final int[] PREDEFINED_ANGLES = {0, 30, 45, 60, 90}; + private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"}; @Override protected Equation createProblem() { - // 首先,调用父类(MiddleSchoolProblemGenerator)的方法, - // 生成一个已经包含平方或开方运算的题目结构。 + // 1. 调用父类(已修正的 MiddleSchoolProblemGenerator)的方法 Equation middleSchoolEquation = super.createProblem(); - // 直接获取父类生成的可变列表进行再次修改 List operands = new ArrayList<>(middleSchoolEquation.getOperands()); List operators = new ArrayList<>(middleSchoolEquation.getOperators()); - // 随机选择一个操作数,用三角函数表达式替换。 - // 这可能替换一个普通数字,也可能替换掉父类生成的 sqrt(x) 或 (x^2) 表达式,增加了多样性。 + // 2. 随机替换一个操作数为三角函数 int modifyIndex = random.nextInt(operands.size()); int angle = PREDEFINED_ANGLES[random.nextInt(PREDEFINED_ANGLES.length)]; + String funcType = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)]; - // 随机选择一个三角函数 - int funcType = random.nextInt(3); - String trigExpression; - - switch (funcType) { - case 0: // sin - trigExpression = "sin(" + angle + ")"; - break; - case 1: // cos - trigExpression = "cos(" + angle + ")"; - break; - default: // tan - if (angle == 90) { - angle = 45; // 避免tan(90)无定义的情况 - } - trigExpression = "tan(" + angle + ")"; - break; + if ("tan".equals(funcType) && angle == 90) { + angle = 45; // 避免 tan(90) } - // 用三角函数的表达式字符串替换随机选择的操作数 + String trigExpression = String.format("%s(%d)", funcType, angle); operands.set(modifyIndex, trigExpression); - // 用修改后的操作数和原有的操作符列表创建最终的高中题目 return new Equation(operands, operators); } } \ No newline at end of file diff --git a/src/IAuthService.java b/src/IAuthService.java new file mode 100644 index 0000000..a2381ea --- /dev/null +++ b/src/IAuthService.java @@ -0,0 +1,17 @@ +import java.util.Optional; + +/** + * 用户认证服务接口。 + * 定义了用户身份验证相关操作的契约。 + */ +public interface IAuthService { + + /** + * 处理用户登录逻辑。 + * + * @param username 用户名 + * @param password 密码 + * @return 如果认证成功,返回一个包含User的Optional;否则返回一个空的Optional。 + */ + Optional login(String username, String password); +} \ No newline at end of file diff --git a/src/IFileService.java b/src/IFileService.java index 5505040..13061cc 100644 --- a/src/IFileService.java +++ b/src/IFileService.java @@ -21,7 +21,7 @@ public interface IFileService { * 加载指定用户的所有历史题目记录。 * * @param user 要加载历史记录的用户 - * @return 一个包含所有历史题目规范化字符串的集合,用于高效查找 + * @return 一个包含所有历史题目字符串的集合,用于高效查找 * @throws IOException 当文件读取失败时抛出 */ Set loadAllProblemHistory(User user) throws IOException; diff --git a/src/IUserRepository.java b/src/IUserRepository.java new file mode 100644 index 0000000..c22caf1 --- /dev/null +++ b/src/IUserRepository.java @@ -0,0 +1,16 @@ +import java.util.Optional; + +/** + * 用户仓库接口。 + * 定义了访问用户数据的统一契约,将业务逻辑与数据源解耦。 + */ +public interface IUserRepository { + + /** + * 根据用户名查找用户。 + * + * @param username 用户的唯一标识符 + * @return 一个包含用户对象的 Optional,如果用户不存在则为空 + */ + Optional findByUsername(String username); +} \ No newline at end of file diff --git a/src/InMemoryUserRepository.java b/src/InMemoryUserRepository.java deleted file mode 100644 index d6313c3..0000000 --- a/src/InMemoryUserRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 用户仓库的内存实现。 - * 在程序启动时,预加载所有预定义的用户账户。 - */ -public class InMemoryUserRepository implements UserRepository { - - private final Map userDatabase = new ConcurrentHashMap<>(); - - public InMemoryUserRepository() { - // 初始化预定义的9个用户账户 - // 小学老师 - userDatabase.put("张三1", new User("张三1", "123", EducationLevel.PRIMARY)); - userDatabase.put("张三2", new User("张三2", "123", EducationLevel.PRIMARY)); - userDatabase.put("张三3", new User("张三3", "123", EducationLevel.PRIMARY)); - // 初中老师 - userDatabase.put("李四1", new User("李四1", "123", EducationLevel.MIDDLE)); - userDatabase.put("李四2", new User("李四2", "123", EducationLevel.MIDDLE)); - userDatabase.put("李四3", new User("李四3", "123", EducationLevel.MIDDLE)); - // 高中老师 - userDatabase.put("王五1", new User("王五1", "123", EducationLevel.HIGH)); - userDatabase.put("王五2", new User("王五2", "123", EducationLevel.HIGH)); - userDatabase.put("王五3", new User("王五3", "123", EducationLevel.HIGH)); - } - - @Override - public Optional findByUsername(String username) { - return Optional.ofNullable(userDatabase.get(username)); - } -} \ No newline at end of file diff --git a/src/InvalidInputException.java b/src/InvalidInputException.java deleted file mode 100644 index d98e57a..0000000 --- a/src/InvalidInputException.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * 自定义异常,用于表示用户输入无效。 - */ -public class InvalidInputException extends Exception { - public InvalidInputException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/src/MiddleSchoolProblemGenerator.java b/src/MiddleSchoolProblemGenerator.java index 19ae647..83f57c3 100644 --- a/src/MiddleSchoolProblemGenerator.java +++ b/src/MiddleSchoolProblemGenerator.java @@ -1,37 +1,34 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** * 初中题目生成器(具体策略)。 - * 基于小学题目,并确保至少包含一个平方或开方运算。 + * 首先生成一个允许出现负数的标准四则运算题目,然后确保至少包含一个平方或开方运算。 */ -public class MiddleSchoolProblemGenerator extends ElementaryProblemGenerator { +public class MiddleSchoolProblemGenerator extends AbstractProblemGenerator { @Override protected Equation createProblem() { - // 首先,借用父类方法生成一个基础的四则运算结构 - Equation basicEquation = super.createProblem(); + // 1. 调用父类的核心方法,并传入 false,允许生成包含负数的标准表达式。 + Equation basicEquation = createBaseArithmeticProblem(false); - // 将父类生成的操作数(Integer)转换为本类需要的 String 列表 - List operands = basicEquation.getOperands().stream() - .map(String::valueOf) - .collect(Collectors.toList()); + // 2. 将基础表达式元素转为可修改的列表 + List operands = new ArrayList<>(basicEquation.getOperands()); List operators = new ArrayList<>(basicEquation.getOperators()); - // 随机选择一个操作数进行变形 + // 3. 随机选择一个操作数进行变形 int modifyIndex = random.nextInt(operands.size()); - int originalValue = Integer.parseInt(operands.get(modifyIndex)); + String targetOperand = operands.get(modifyIndex).replace("(", "").replace(")", ""); - // 随机决定是进行平方还是开方运算 + // 4. 随机决定是进行平方还是开方运算 if (random.nextBoolean()) { - // 平方运算:生成 "(originalValue^2)" 格式的字符串 - String squaredExpression = String.format("(%d^2)", originalValue); + // 平方运算 + String squaredExpression = String.format("(%s^2)", targetOperand); operands.set(modifyIndex, squaredExpression); } else { - // 开方运算:生成 "sqrt(value)" 格式的字符串 + // 开方运算 int base = getRandomNumber(2, 10); - int valueToRoot = base * base; // 确保可以完美开方 + int valueToRoot = base * base; String sqrtExpression = String.format("sqrt(%d)", valueToRoot); operands.set(modifyIndex, sqrtExpression); } diff --git a/src/Operator.java b/src/Operator.java index 8597142..e1729cb 100644 --- a/src/Operator.java +++ b/src/Operator.java @@ -1,54 +1,29 @@ -import java.util.function.BinaryOperator; - /** * 数学运算符枚举。 - * 封装了运算符的符号和其对应的计算逻辑。 - * 这种设计符合开闭原则,易于扩展新的运算符。 + * 封装了运算符的符号及其属性,如是否为可交换运算符。 */ public enum Operator { - ADD('+', (a, b) -> a + b, true), - SUBTRACT('-', (a, b) -> a - b, false), - MULTIPLY('*', (a, b) -> a * b, true), - DIVIDE('/', (a, b) -> a / b, false); + ADD('+'), + SUBTRACT('-'), + MULTIPLY('*'), + DIVIDE('/'); private final char symbol; - private final BinaryOperator operation; - private final boolean isCommutative; // 标记是否为可交换运算符(如加法、乘法) /** * 构造函数。 - * * @param symbol 运算符的字符表示 - * @param operation 封装了计算逻辑的函数式接口 - * @param isCommutative 运算符是否满足交换律 */ - Operator(char symbol, BinaryOperator operation, boolean isCommutative) { + Operator(char symbol) { this.symbol = symbol; - this.operation = operation; - this.isCommutative = isCommutative; } + /** + * 获取运算符的字符表示。 + * @return 符号字符 + */ public char getSymbol() { return symbol; } - public boolean isCommutative() { - return isCommutative; - } - - /** - * 应用此运算符进行计算。 - * - * @param a 第一个操作数 - * @param b 第二个操作数 - * @return 计算结果 - */ - public int apply(int a, int b) { - // 为除法增加安全检查,防止除以零 - if (this == DIVIDE && b == 0) { - // 在实际生成中,应避免生成除数为0的题目,这里作为最后防线 - throw new ArithmeticException("Division by zero."); - } - return operation.apply(a, b); - } } \ No newline at end of file diff --git a/src/SessionManager.java b/src/SessionManager.java index 7612401..633a97c 100644 --- a/src/SessionManager.java +++ b/src/SessionManager.java @@ -2,13 +2,14 @@ * 会话管理器(单例模式)。 * 管理应用程序的运行时状态,包括当前登录的用户和选择的题目生成策略。 */ -public final class SessionManager { +public final class SessionManager extends AbstractSessionManager { // 懒汉式单例模式,确保线程安全 private static volatile SessionManager instance; private User currentUser; private IProblemGenerator currentGenerator; + private String currentLevel; private SessionManager() { // 私有构造函数,防止外部实例化 @@ -38,7 +39,7 @@ public final class SessionManager { public void startSession(User user) { this.currentUser = user; // 根据用户的默认级别,初始化对应的题目生成策略 - setGeneratorByDefaultLevel(user.getDefaultLevel()); + setGeneratorByLevel(user.getDefaultLevel()); } /** @@ -66,43 +67,37 @@ public final class SessionManager { * * @param level 新的教育级别 */ - public void setCurrentGenerator(EducationLevel level) { + public void setCurrentGenerator(String level) { if (level == null) { throw new IllegalArgumentException("难度等级不可为空。"); } - setGeneratorByDefaultLevel(level); + setGeneratorByLevel(level); } - private void setGeneratorByDefaultLevel(EducationLevel level) { + private void setGeneratorByLevel(String level) { + String tempLevel = this.currentLevel; + this.currentLevel = level; switch (level) { - case PRIMARY: + case "小学": this.currentGenerator = new ElementaryProblemGenerator(); break; - case MIDDLE: + case "初中": this.currentGenerator = new MiddleSchoolProblemGenerator(); break; - case HIGH: + case "高中": this.currentGenerator = new HighSchoolProblemGenerator(); break; default: - // 理论上不会发生,因为枚举是固定的 - throw new IllegalStateException("错误的难度等级: " + level); + System.out.println("错误的难度等级: " + level); + this.currentLevel = tempLevel; } } /** * 获取当前选定级别的中文名称。 - * * @return 中文级别名称,如 "小学" */ - public String getCurrentLevelName() { - if (currentGenerator instanceof HighSchoolProblemGenerator) { - return "高中"; - } else if (currentGenerator instanceof MiddleSchoolProblemGenerator) { - return "初中"; - }else if (currentGenerator instanceof ElementaryProblemGenerator) { - return "小学"; - } - return "未知"; + public String getCurrentLevel() { + return currentLevel; } } \ No newline at end of file diff --git a/src/User.java b/src/User.java index 67025cd..74fa5ec 100644 --- a/src/User.java +++ b/src/User.java @@ -1,33 +1,18 @@ -import java.util.Objects; +// 修改后的 User.java +public class User extends AbstractUser { -/** - * 用户实体类。 - * 这是一个简单的POJO,用于封装用户的静态、持久化信息。 - */ -public class User { - - private final String username; - private final String password; // 注意:在生产系统中应存储哈希值 - private final EducationLevel defaultLevel; + private final String defaultLevel; private final String storagePath; - public User(String username, String password, EducationLevel defaultLevel) { - this.username = username; - this.password = password; + public User(String username, String password, String defaultLevel) { + // 调用父类的构造函数来初始化 username 和 password + super(username, password); this.defaultLevel = defaultLevel; // 根据用户名生成唯一的存储路径 - this.storagePath = "./data/" + username + "/"; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; + this.storagePath = "./data/" + this.username + "/"; } - public EducationLevel getDefaultLevel() { + public String getDefaultLevel() { return defaultLevel; } @@ -35,20 +20,5 @@ public class User { return storagePath; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - User user = (User) o; - return username.equals(user.username); - } - - @Override - public int hashCode() { - return Objects.hash(username); - } + // equals() 和 hashCode() 方法已由父类 AbstractUser 提供 } \ No newline at end of file diff --git a/src/UserRepository.java b/src/UserRepository.java index 4ab7ee8..0812340 100644 --- a/src/UserRepository.java +++ b/src/UserRepository.java @@ -1,16 +1,33 @@ +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; /** - * 用户仓库接口。 - * 定义了访问用户数据的统一契约,将业务逻辑与数据源解耦。 + * 用户仓库的内存实现。 + * 在程序启动时,预加载所有预定义的用户账户。 */ -public interface UserRepository { +public class UserRepository implements IUserRepository { - /** - * 根据用户名查找用户。 - * - * @param username 用户的唯一标识符 - * @return 一个包含用户对象的 Optional,如果用户不存在则为空 - */ - Optional findByUsername(String username); + private final Map userDatabase = new ConcurrentHashMap<>(); + + public UserRepository() { + // 初始化预定义的9个用户账户 + // 小学老师 + userDatabase.put("张三1", new User("张三1", "123", "小学")); + userDatabase.put("张三2", new User("张三2", "123", "小学")); + userDatabase.put("张三3", new User("张三3", "123", "小学")); + // 初中老师 + userDatabase.put("李四1", new User("李四1", "123", "初中")); + userDatabase.put("李四2", new User("李四2", "123", "初中")); + userDatabase.put("李四3", new User("李四3", "123", "初中")); + // 高中老师 + userDatabase.put("王五1", new User("王五1", "123", "高中")); + userDatabase.put("王五2", new User("王五2", "123", "高中")); + userDatabase.put("王五3", new User("王五3", "123", "高中")); + } + + @Override + public Optional findByUsername(String username) { + return Optional.ofNullable(userDatabase.get(username)); + } } \ No newline at end of file