Skip to content

Commit 0bf2999

Browse files
committed
feat(pattern-composite): add composite pattern implementation and related documentation and sample code
1 parent 9ac017d commit 0bf2999

File tree

19 files changed

+725
-6
lines changed

19 files changed

+725
-6
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# 命令模式 (Command Pattern)
2+
3+
## 介绍
4+
5+
命令模式是一种行为型设计模式,它将请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。在本项目中,我们通过实现一个简单的文本编辑器来展示命令模式的应用。
6+
7+
## 核心组件
8+
9+
1. **Command接口**
10+
```java
11+
public interface Command {
12+
void execute();
13+
void undo();
14+
}
15+
```
16+
17+
2. **具体命令类**
18+
- `AddCommand`: 添加文本命令
19+
- `DeleteCommand`: 删除文本命令
20+
- `CopyCommand`: 复制文本命令
21+
- `PasteCommand`: 粘贴文本命令
22+
23+
3. **接收者**
24+
- `TextEditor`: 实际执行命令的类,包含文本编辑的核心功能
25+
26+
4. **调用者**
27+
- `Invoker`: 负责调用命令,维护命令历史,支持撤销操作
28+
29+
## 实现细节
30+
31+
1. **命令封装**
32+
- 每个命令都封装了具体的操作和撤销操作
33+
- 命令对象持有对接收者(TextEditor)的引用
34+
35+
2. **命令历史**
36+
- Invoker维护命令历史列表
37+
- 支持撤销操作,可以按顺序撤销已执行的命令
38+
39+
3. **文本编辑器功能**
40+
- 支持基本的文本操作:添加、删除、复制、粘贴
41+
- 使用系统剪贴板实现复制粘贴功能
42+
43+
## 使用示例
44+
45+
```java
46+
TextEditor editor = new TextEditor();
47+
Command add = new AddCommand(editor, "Command pattern in text editor.\n");
48+
Command copy = new CopyCommand(editor);
49+
Command add1 = new AddCommand(editor, "---\n");
50+
Command paste = new PasteCommand(editor);
51+
52+
Invoker invoker = new Invoker();
53+
invoker.invoke(add)
54+
.invoke(copy)
55+
.invoke(add1)
56+
.invoke(paste);
57+
58+
// 撤销操作
59+
invoker.undo();
60+
```
61+
62+
## 模式优势
63+
64+
1. **解耦**: 将请求发送者和接收者解耦
65+
2. **可扩展**: 易于添加新的命令,无需修改现有代码
66+
3. **可撤销**: 支持操作的撤销和重做
67+
4. **命令队列**: 支持命令的排队执行
68+
5. **日志记录**: 可以记录命令执行历史
69+
70+
## 参考
71+
72+
> [Command Pattern](https://refactoringguru.cn/design-patterns/command)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# 组合模式 (Composite Pattern)
2+
3+
## 介绍
4+
5+
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示"部分-整体"的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
6+
7+
在本项目中,我们通过一个表达式计算器的实现来展示组合模式的应用。该实现支持:
8+
- 基本数学运算(加、减、乘、除)
9+
- 数字表达式
10+
- 变量表达式
11+
- 复杂表达式的解析和计算
12+
13+
## 核心组件
14+
15+
1. **Expression接口**
16+
- 定义了所有表达式对象的通用接口
17+
- 包含`getValue()`方法用于计算表达式的值
18+
19+
2. **具体表达式类**
20+
- `NumberExpression`: 表示数字常量
21+
- `VariableExpression`: 表示变量
22+
- `AddExpression`: 加法运算
23+
- `SubtractExpression`: 减法运算
24+
- `MultiplyExpression`: 乘法运算
25+
- `DivisionExpression`: 除法运算
26+
27+
3. **表达式解析器**
28+
- `ExpressionParser`: 负责将字符串表达式解析为表达式对象树
29+
30+
## 使用示例
31+
32+
```java
33+
// 解析简单表达式
34+
String expressionString = "(1+2)*(3+4)/2";
35+
ExpressionParser parser = new ExpressionParser(expressionString);
36+
Expression expression = parser.parse();
37+
System.out.println(expression.getValue());
38+
39+
// 使用变量的表达式
40+
Map<String, Integer> variables = Map.of("a", 10, "b", 20);
41+
ExpressionParser parserWithVars = new ExpressionParser("a + b * 10 / 9", variables);
42+
Expression expressionWithVars = parserWithVars.parse();
43+
System.out.println(expressionWithVars.getValue());
44+
```
45+
46+
## 模式优势
47+
48+
1. **统一处理**: 客户端可以用统一的方式处理单个对象和组合对象
49+
2. **灵活性**: 可以轻松添加新的表达式类型
50+
3. **可扩展性**: 支持复杂的表达式组合
51+
4. **维护性**: 代码结构清晰,易于维护和扩展
52+
53+
## 参考
54+
55+
> [Composite Pattern](https://refactoringguru.cn/design-patterns/composite)
56+
> [【设计模式实战】组合模式](https://www.bilibili.com/video/BV1h8A2eSEfy/)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.helltractor.demo</groupId>
8+
<artifactId>pattern-design</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>pattern-composite</artifactId>
13+
14+
<properties>
15+
<maven.compiler.source>21</maven.compiler.source>
16+
<maven.compiler.target>21</maven.compiler.target>
17+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18+
</properties>
19+
20+
</project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.helltractor.demo.calc;
2+
3+
public class AddExpression implements Expression {
4+
5+
private Expression left;
6+
private Expression right;
7+
8+
public AddExpression (Expression left, Expression right) {
9+
this.left = left;
10+
this.right = right;
11+
}
12+
13+
@Override
14+
public int getValue() {
15+
return left.getValue() + right.getValue();
16+
}
17+
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.helltractor.demo.calc;
2+
3+
public abstract class BinaryOperationExpression implements Expression {
4+
5+
private Expression left;
6+
private Expression right;
7+
8+
protected BinaryOperationExpression(Expression left, Expression right) {
9+
this.left = left;
10+
this.right = right;
11+
}
12+
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.helltractor.demo.calc;
2+
3+
import java.util.Map;
4+
5+
public class CalculateDemo {
6+
7+
public static void main(String[] args) {
8+
String expressionString = "(1+2)*(3+4)/2";
9+
ExpressionParser expressionParser = new ExpressionParser(expressionString);
10+
Expression parse = expressionParser.parse();
11+
System.out.println("Parsed Expression: " + parse.getValue()); // Output: Parsed Expression: (1 + 2) * (3 + 4) / 2
12+
13+
int a = 10;
14+
int b = 20;
15+
ExpressionParser expressionParserWithVars = new ExpressionParser("a + b * 10 / 9", Map.of("a", a, "b", b));
16+
Expression parseWithVars = expressionParserWithVars.parse();
17+
System.out.println("Parsed Expression with Variables: " + parseWithVars.getValue()); // Output: Parsed Expression with Variables: 30
18+
}
19+
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.helltractor.demo.calc;
2+
3+
public class DivisionExpression implements Expression {
4+
5+
private Expression left;
6+
private Expression right;
7+
8+
public DivisionExpression(Expression left, Expression right) {
9+
this.left = left;
10+
this.right = right;
11+
}
12+
13+
@Override
14+
public int getValue() {
15+
if (right.getValue() == 0) {
16+
throw new ArithmeticException("Division by zero is not allowed.");
17+
}
18+
return left.getValue() / right.getValue();
19+
}
20+
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.helltractor.demo.calc;
2+
3+
public interface Expression {
4+
5+
int getValue();
6+
7+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.helltractor.demo.calc;
2+
3+
import java.util.*;
4+
5+
public class ExpressionParser {
6+
7+
private final String expression;
8+
private final Map<String, Integer> variables = new HashMap<>();
9+
private int point = 0;
10+
11+
public ExpressionParser(String expression) {
12+
this.expression = expression;
13+
}
14+
15+
public ExpressionParser(String expression, Map<String, Integer> variables) {
16+
this.expression = expression;
17+
this.variables.putAll(variables);
18+
}
19+
20+
public List<String> toSuffix() {
21+
List<String> suffix = new ArrayList<>();
22+
List<String> stack = new ArrayList<>();
23+
24+
while (point < expression.length()) {
25+
char c = expression.charAt(point);
26+
if (Character.isWhitespace(c)) {
27+
point++;
28+
continue;
29+
} else if (c == '(') {
30+
stack.add(String.valueOf(c));
31+
} else if (c == ')') {
32+
while (!stack.getLast().equals("(")) {
33+
suffix.add(stack.removeLast());
34+
}
35+
stack.removeLast();
36+
} else if (c == '*' || c == '/') {
37+
while (!stack.isEmpty() && (stack.getLast().equals("*") || stack.getLast().equals("/"))) {
38+
suffix.add(stack.removeLast());
39+
}
40+
stack.add(String.valueOf(c));
41+
} else if (c == '+' || c == '-') {
42+
while (topIsOperator(stack)) {
43+
suffix.add(stack.removeLast());
44+
}
45+
stack.add(String.valueOf(c));
46+
} else if (Character.isLetter(c)) {
47+
StringBuilder variable = new StringBuilder();
48+
while (point < expression.length() && Character.isLetterOrDigit(expression.charAt(point))) {
49+
variable.append(expression.charAt(point++));
50+
}
51+
point--;
52+
suffix.add(variable.toString());
53+
} else if (Character.isDigit(c)) {
54+
StringBuilder number = new StringBuilder();
55+
while (point < expression.length() && Character.isDigit(expression.charAt(point))) {
56+
number.append(expression.charAt(point++));
57+
}
58+
point--;
59+
suffix.add(number.toString());
60+
} else {
61+
throw new IllegalArgumentException("Invalid character in expression: " + c);
62+
}
63+
point++;
64+
}
65+
while (!stack.isEmpty()) {
66+
suffix.add(stack.removeLast());
67+
}
68+
return suffix;
69+
}
70+
71+
public Expression parse() {
72+
List<String> suffix = this.toSuffix();
73+
List<Expression> stack = new ArrayList<>();
74+
75+
for (String token : suffix) {
76+
if (variables.containsKey(token)) {
77+
stack.add(new VariableExpression(token, variables));
78+
} else if (Character.isDigit(token.charAt(0))) {
79+
stack.add(new NumberExpression(Integer.parseInt(token)));
80+
} else if (token.equals("+")) {
81+
Expression right = stack.removeLast();
82+
Expression left = stack.removeLast();
83+
stack.add(new AddExpression(left, right));
84+
} else if (token.equals("-")) {
85+
Expression right = stack.removeLast();
86+
Expression left = stack.removeLast();
87+
stack.add(new SubtractExpression(left, right));
88+
} else if (token.equals("*")) {
89+
Expression right = stack.removeLast();
90+
Expression left = stack.removeLast();
91+
stack.add(new MultiplyExpression(left, right));
92+
} else if (token.equals("/")) {
93+
Expression right = stack.removeLast();
94+
Expression left = stack.removeLast();
95+
stack.add(new DivisionExpression(left, right));
96+
} else {
97+
throw new IllegalArgumentException("Unknown token: " + token);
98+
}
99+
}
100+
101+
return stack.getLast();
102+
}
103+
104+
private boolean topIsOperator(List<String> stack) {
105+
if (stack.isEmpty()) {
106+
return false;
107+
}
108+
return Set.of("+", "-", "*", "/").contains(stack.getLast());
109+
}
110+
111+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.helltractor.demo.calc;
2+
3+
public class MultiplyExpression implements Expression {
4+
5+
private Expression left;
6+
private Expression right;
7+
8+
public MultiplyExpression(Expression left, Expression right) {
9+
this.left = left;
10+
this.right = right;
11+
}
12+
13+
@Override
14+
public int getValue() {
15+
return left.getValue() * right.getValue();
16+
}
17+
18+
}

0 commit comments

Comments
 (0)