12 changed files with 299 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||
|
*.jar |
||||
|
*.jar |
||||
|
*.class |
||||
|
*.log |
||||
@ -0,0 +1,9 @@ |
|||||
|
package com.example.datacollect.command; |
||||
|
|
||||
|
import com.example.datacollect.model.Article; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public interface Command { |
||||
|
String getName(); |
||||
|
void execute(String[] args, List<Article> articles); |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
package com.example.datacollect.command; |
||||
|
|
||||
|
import com.example.datacollect.model.Article; |
||||
|
import com.example.datacollect.util.UrlValidator; |
||||
|
import com.example.datacollect.view.ConsoleView; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 爬取命令(带 URL 验证) |
||||
|
*/ |
||||
|
public class CrawlCommand implements Command { |
||||
|
private final ConsoleView view; |
||||
|
|
||||
|
public CrawlCommand(ConsoleView view) { |
||||
|
this.view = view; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "crawl"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取命令别名 |
||||
|
*/ |
||||
|
public String[] getAliases() { |
||||
|
return new String[]{"c"}; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void execute(String[] args, List<Article> articles) { |
||||
|
if (args.length < 2) { |
||||
|
view.printError("Usage: crawl <url> 或 c <url>"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String url = args[1]; |
||||
|
|
||||
|
// URL 格式验证
|
||||
|
if (!UrlValidator.isValid(url)) { |
||||
|
view.printError("URL 格式错误:" + UrlValidator.getValidationError(url)); |
||||
|
view.printInfo("示例:crawl https://www.example.com"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
view.printInfo("正在爬取:" + url); |
||||
|
// TODO: 实现实际爬取逻辑
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
package com.example.datacollect.command; |
||||
|
|
||||
|
import com.example.datacollect.model.Article; |
||||
|
import com.example.datacollect.view.ConsoleView; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class ExitCommand implements Command { |
||||
|
private final ConsoleView view; |
||||
|
|
||||
|
public ExitCommand(ConsoleView view) { |
||||
|
this.view = view; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "exit"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void execute(String[] args, List<Article> articles) { |
||||
|
view.printSuccess("Bye!"); |
||||
|
System.exit(0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
package com.example.datacollect.command; |
||||
|
|
||||
|
import com.example.datacollect.model.Article; |
||||
|
import com.example.datacollect.view.ConsoleView; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class HelpCommand implements Command { |
||||
|
private final ConsoleView view; |
||||
|
|
||||
|
public HelpCommand(ConsoleView view) { |
||||
|
this.view = view; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "help"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void execute(String[] args, List<Article> articles) { |
||||
|
view.printInfo("Commands: crawl <url>, list, help, exit"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
package com.example.datacollect.command; |
||||
|
|
||||
|
import com.example.datacollect.model.Article; |
||||
|
import com.example.datacollect.view.ConsoleView; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 查看历史命令 |
||||
|
*/ |
||||
|
public class History implements Command { |
||||
|
private HistoryCommand historyCommand; |
||||
|
|
||||
|
public History(HistoryCommand historyCommand) { |
||||
|
this.historyCommand = historyCommand; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "history"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void execute(String[] args, List<Article> articles) { |
||||
|
ConsoleView view = new ConsoleView(); |
||||
|
historyCommand.showHistory(view, articles); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
# List<Article> 共享引用的风险分析 |
||||
|
|
||||
|
## 问题描述 |
||||
|
在 Main.java 中,`List<Article> articles` 被传递给多个对象(CrawlerController、Command 等),这存在共享引用风险。 |
||||
|
|
||||
|
## 主要风险 |
||||
|
|
||||
|
### 1. 数据不一致风险 |
||||
|
多个对象持有同一个 List 的引用,任何地方对 List 的修改都会影响其他地方。例如: |
||||
|
- CrawlCommand 添加了新 Article |
||||
|
- ListCommand 在遍历 |
||||
|
- 如果同时修改,可能导致 ConcurrentModificationException |
||||
|
|
||||
|
### 2. 封装性破坏 |
||||
|
List 在多个类之间共享,违反了封装原则: |
||||
|
- 任何持有引用的类都可以随意修改 List |
||||
|
- 无法追踪是谁、在什么时候修改了数据 |
||||
|
- 难以进行数据验证和完整性检查 |
||||
|
|
||||
|
### 3. 线程安全问题 |
||||
|
如果未来引入多线程: |
||||
|
- 多个线程同时访问同一个 List |
||||
|
- 没有同步机制会导致数据损坏 |
||||
|
- 可能出现脏读、丢失更新等问题 |
||||
|
|
||||
|
### 4. 测试困难 |
||||
|
- 单元测试时难以隔离 |
||||
|
- 一个测试可能影响另一个测试的结果 |
||||
|
- 需要手动清理共享状态 |
||||
|
|
||||
|
## 解决方案 |
||||
|
|
||||
|
### 方案 1:使用不可变集合 |
||||
|
```java |
||||
|
// 返回不可变列表 |
||||
|
public List<Article> getArticles() { |
||||
|
return Collections.unmodifiableList(articles); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 方案 2:防御性拷贝 |
||||
|
```java |
||||
|
// 创建副本 |
||||
|
public List<Article> getArticlesCopy() { |
||||
|
return new ArrayList<>(articles); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 方案 3:使用 Repository 模式 |
||||
|
```java |
||||
|
// 通过 Repository 管理,不直接暴露 List |
||||
|
public class ArticleRepository { |
||||
|
private List<Article> articles = new ArrayList<>(); |
||||
|
|
||||
|
public void add(Article article) { } |
||||
|
public List<Article> findAll() { |
||||
|
return new ArrayList<>(articles); // 返回副本 |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 总结 |
||||
|
共享引用虽然方便,但会带来数据一致性、封装性、线程安全等问题。 |
||||
|
最佳实践是: |
||||
|
1. 最小化共享 |
||||
|
2. 必要时使用防御性拷贝 |
||||
|
3. 使用不可变集合 |
||||
|
4. 通过接口/Repository 封装访问 |
||||
|
|
||||
|
(字数:约 450 字) |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.example.datacollect; |
||||
|
|
||||
|
import com.example.datacollect.command.HistoryCommand; |
||||
|
import com.example.datacollect.controller.CrawlerController; |
||||
|
import com.example.datacollect.model.Article; |
||||
|
import com.example.datacollect.view.ConsoleView; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class Main { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
ConsoleView view = new ConsoleView(); |
||||
|
List<Article> articles = new ArrayList<>(); |
||||
|
HistoryCommand historyCommand = new HistoryCommand(); |
||||
|
CrawlerController controller = new CrawlerController(view, articles, historyCommand); |
||||
|
|
||||
|
view.printSuccess("Welcome to CLI Crawler (w9_1)! Type help for commands."); |
||||
|
while (true) { |
||||
|
String input = view.readLine(); |
||||
|
historyCommand.record(input); // 记录用户输入的命令
|
||||
|
controller.handle(input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
# DataCollect 教学项目 — 最小可运行版本 |
||||
|
|
||||
|
这是一个最小可用的 Java CLI 演示工程,目标:打印帮助信息以验证运行环境。 |
||||
|
|
||||
|
构建: |
||||
|
```bash |
||||
|
mvn -q package |
||||
|
``` |
||||
|
|
||||
|
运行(示例): |
||||
|
```bash |
||||
|
java -jar target/datacollect-cli-0.1.0-jar-with-dependencies.jar --help |
||||
|
``` |
||||
|
|
||||
|
项目结构(最小): |
||||
|
- `src/main/java/com/example/datacollect/Main.java` — CLI 入口,打印帮助 |
||||
|
- `pom.xml` — Maven 构建配置,生成可执行 jar |
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,51 @@ |
|||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<groupId>com.example</groupId> |
||||
|
<artifactId>datacollect-cli</artifactId> |
||||
|
<version>0.1.0</version> |
||||
|
<properties> |
||||
|
<maven.compiler.source>11</maven.compiler.source> |
||||
|
<maven.compiler.target>11</maven.compiler.target> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
</properties> |
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-compiler-plugin</artifactId> |
||||
|
<version>3.11.0</version> |
||||
|
<configuration> |
||||
|
<source>11</source> |
||||
|
<target>11</target> |
||||
|
<encoding>UTF-8</encoding> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-assembly-plugin</artifactId> |
||||
|
<version>3.3.0</version> |
||||
|
<configuration> |
||||
|
<archive> |
||||
|
<manifest> |
||||
|
<mainClass>com.example.datacollect.Main</mainClass> |
||||
|
</manifest> |
||||
|
</archive> |
||||
|
<descriptorRefs> |
||||
|
<descriptorRef>jar-with-dependencies</descriptorRef> |
||||
|
</descriptorRefs> |
||||
|
</configuration> |
||||
|
<executions> |
||||
|
<execution> |
||||
|
<id>make-assembly</id> |
||||
|
<phase>package</phase> |
||||
|
<goals> |
||||
|
<goal>single</goal> |
||||
|
</goals> |
||||
|
</execution> |
||||
|
</executions> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
</project> |
||||
Loading…
Reference in new issue