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