diff --git a/w10/AI架构审计 b/w10/AI架构审计 new file mode 100644 index 0000000..f505253 --- /dev/null +++ b/w10/AI架构审计 @@ -0,0 +1,18 @@ +检查策略解耦与封装 +解耦方面:策略接口、工厂、使用者三层结构清晰,使用者与具体爬取算法完全解耦,新增 / 修改网站策略不影响上层命令与主程序,符合开闭原则;仅主程序存在少量硬编码注册,不影响整体解耦效果。 +2. 封装方面:策略实现细节、策略集合、数据模型均做到私有隐藏、接口暴露,外部只能通过规范接口交互,有效隔离内部变化,封装完整、安全。 +3. 整体架构:策略模式 + 命令模式 + 工厂模式组合,职责清晰、依赖单向、无循环依赖,具备良好的可维护性与扩展性。 + +检查开闭原则达成度等 +审计结论: +1. 开闭原则:项目采用命令模式 + 策略模式,新增命令只需实现Command2接口、新增策略只需实现CrawlStrategy2接口,无需修改主程序和工厂代码,完全符合对扩展开放、对修改关闭的原则。 +2. Repository 封装:ArticleRepository内部集合用private final修饰,外部仅能通过add/addAll方法添加,且返回的是Collections.unmodifiableList不可修改视图,同时对null做了严格防御,封装完备,数据安全。 +3. 循环依赖:所有组件均通过构造方法依赖注入,依赖方向为Main2 → 命令/工厂/仓库 → 模型,无反向依赖,不存在循环依赖问题。 + +进阶探究题答案 +题目:不用工厂,直接用Map存起来 vs StrategyFactory的区别? +直接用Map存储策略,虽然实现简单,但存在明显局限: +1. 耦合度高:调用方需要直接维护Map的注册和匹配逻辑,新增策略时需要修改调用方代码,违反开闭原则。 +2. 扩展性差:无法在匹配逻辑中添加复杂规则(如优先级、默认策略、正则匹配),后续扩展困难。 +3. 可维护性低:匹配逻辑散落在业务代码中,难以统一管理和修改。 +而StrategyFactory将策略的注册、匹配逻辑封装为独立组件,调用方仅依赖工厂接口,无需关心具体实现;新增策略只需注册到工厂,不影响其他代码,既降低耦合,又提升了扩展性和可维护性,更符合面向对象设计规范。 \ No newline at end of file diff --git a/w10/AnalyzeCommand2.java b/w10/AnalyzeCommand2.java new file mode 100644 index 0000000..5e48d03 --- /dev/null +++ b/w10/AnalyzeCommand2.java @@ -0,0 +1,34 @@ +import java.util.List; + +public class AnalyzeCommand2 implements Command2 { + private final StrategyFactory2 factory; + + public AnalyzeCommand2(StrategyFactory2 factory) { + this.factory = factory; + } + + @Override + public String getName() { + return "analyze"; + } + + @Override + public void execute(String[] args, List articles) { + if (args == null || args.length == 0) { + System.out.println("格式:analyze 网址"); + return; + } + + String url = args[0]; + CrawlStrategy2 strategy = factory.getMatchStrategy(url); + + System.out.println("===== 分析结果 ====="); + if (strategy != null) { + System.out.println("链接:" + url); + System.out.println("状态:可以解析 ✅"); + } else { + System.out.println("链接:" + url); + System.out.println("状态:不支持 ❌"); + } + } +} \ No newline at end of file diff --git a/w10/Article2.java b/w10/Article2.java new file mode 100644 index 0000000..2a35349 --- /dev/null +++ b/w10/Article2.java @@ -0,0 +1,27 @@ +public class Article2 { + private String title; + private String url; + private String content; + private String author; + private String publishDate; + + public Article2(String title, String url, String content) { + this.title = title; + this.url = url; + this.content = content; + } + + public Article2(String title, String url, String content, String author, String publishDate) { + this.title = title; + this.url = url; + this.content = content; + this.author = author; + this.publishDate = publishDate; + } + + public String getTitle() { return title; } + public String getUrl() { return url; } + public String getContent() { return content; } + public String getAuthor() { return author; } + public String getPublishDate() { return publishDate; } +} \ No newline at end of file diff --git a/w10/ArticleRepository.java b/w10/ArticleRepository.java new file mode 100644 index 0000000..78c82ef --- /dev/null +++ b/w10/ArticleRepository.java @@ -0,0 +1,32 @@ +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ArticleRepository { + private final List articles = new ArrayList<>(); + + public void add(Article2 article) { + if (article == null) throw new IllegalArgumentException("文章不能为空"); + articles.add(article); + } + + public void addAll(List newArticles) { + if (newArticles == null) throw new IllegalArgumentException("列表不能为空"); + for (Article2 a : newArticles) { + if (a == null) throw new IllegalArgumentException("列表中不能有空对象"); + articles.add(a); + } + } + + public List getAll() { + return Collections.unmodifiableList(articles); + } + + public int size() { + return articles.size(); + } + + public void clear() { + articles.clear(); + } +} \ No newline at end of file diff --git a/w10/Command2.java b/w10/Command2.java new file mode 100644 index 0000000..c232b76 --- /dev/null +++ b/w10/Command2.java @@ -0,0 +1,6 @@ +import java.util.List; + +public interface Command2 { + String getName(); + void execute(String[] args, List articles); +} \ No newline at end of file diff --git a/w10/CrawlStrategy2.java b/w10/CrawlStrategy2.java new file mode 100644 index 0000000..52e3310 --- /dev/null +++ b/w10/CrawlStrategy2.java @@ -0,0 +1,4 @@ +public interface CrawlStrategy2 { + boolean supports(String url); + Article2 crawl(String url); +} \ No newline at end of file diff --git a/w10/HistoryCommand2.java b/w10/HistoryCommand2.java new file mode 100644 index 0000000..72da853 --- /dev/null +++ b/w10/HistoryCommand2.java @@ -0,0 +1,27 @@ +import java.util.ArrayList; +import java.util.List; + +public class HistoryCommand2 implements Command2 { + private final List commandHistory = new ArrayList<>(); + + @Override + public String getName() { + return "history"; + } + + @Override + public void execute(String[] args, List articles) { + if (commandHistory.isEmpty()) { + System.out.println("暂无历史记录"); + return; + } + System.out.println("===== 命令历史 ====="); + for (int i = 0; i < commandHistory.size(); i++) { + System.out.println((i+1) + ". " + commandHistory.get(i)); + } + } + + public void addCommand(String cmd) { + commandHistory.add(cmd); + } +} diff --git a/w10/Main3.java b/w10/Main3.java new file mode 100644 index 0000000..c4f9585 --- /dev/null +++ b/w10/Main3.java @@ -0,0 +1,56 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Main3 { + public static void main(String[] args) { + StrategyFactory2 factory = new StrategyFactory2(); + + // 模拟一个博客策略 + CrawlStrategy2 blogStrategy = new CrawlStrategy2() { + @Override + public boolean supports(String url) { + return url.contains("blog"); + } + + @Override + public Article2 crawl(String url) { + return new Article2("测试标题", url, "测试内容", "作者", "2025-01-01"); + } + }; + + factory.registerStrategy(blogStrategy); + + AnalyzeCommand2 analyzeCmd = new AnalyzeCommand2(factory); + HistoryCommand2 historyCmd = new HistoryCommand2(); + List articles = new ArrayList<>(); + + Scanner sc = new Scanner(System.in); + System.out.println("===== 程序启动 ====="); + System.out.println("命令:"); + System.out.println("analyze 网址"); + System.out.println("history"); + System.out.println("exit\n"); + + while (true) { + System.out.print("请输入命令:"); + String input = sc.nextLine().trim(); + historyCmd.addCommand(input); + + if (input.equalsIgnoreCase("exit")) { + System.out.println("退出"); + break; + } + + if (input.startsWith("analyze ")) { + String[] arr = input.split(" ", 2); + analyzeCmd.execute(new String[]{arr[1]}, articles); + } else if (input.equalsIgnoreCase("history")) { + historyCmd.execute(new String[0], articles); + } else { + System.out.println("未知命令"); + } + } + sc.close(); + } +} \ No newline at end of file diff --git a/w10/StrategyFactory2.java b/w10/StrategyFactory2.java new file mode 100644 index 0000000..be77698 --- /dev/null +++ b/w10/StrategyFactory2.java @@ -0,0 +1,17 @@ +import java.util.ArrayList; +import java.util.List; + +public class StrategyFactory2 { + private final List strategies = new ArrayList<>(); + + public void registerStrategy(CrawlStrategy2 strategy) { + strategies.add(strategy); + } + + public CrawlStrategy2 getMatchStrategy(String url) { + for (CrawlStrategy2 s : strategies) { + if (s.supports(url)) return s; + } + return null; + } +} \ No newline at end of file diff --git a/w10/类依赖图.png b/w10/类依赖图.png new file mode 100644 index 0000000..84e8ae9 Binary files /dev/null and b/w10/类依赖图.png differ