从零到一,掌握Java简单工厂模式

每天一个 Linux 命令

ls 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-a: 显示所有档案及目录(ls 内定将档案名或目录名称为“.”的视为影藏, 不会列出);
-A: 显示除影藏文件“.”和“..”以外的所有文件列表;
-C: 多列显示输出结果. 这是默认选项;
-l: 与“-C”选项功能相反, 所有输出信息用单列格式输出, 不输出为多列;
-F: 在每个输出项后追加文件的类型标识符, 具体含义: “*”表示具有可执行权限的普通文件, “/”表示目录, “@”表示符号链接, “|”表示命令管道 FIFO, “=”表示 sockets 套接字. 当文件为普通文件时, 不输出任何标识符;
-b: 将文件中的不可输出的字符以反斜线“”加字符编码的方式输出;
-c: 与“-lt”选项连用时, 按照文件状态时间排序输出目录内容, 排序的依据是文件的索引节点中的 ctime 字段. 与“-l”选项连用时, 则排序的一句是文件的状态改变时间;
-d: 仅显示目录名, 而不显示目录下的内容列表. 显示符号链接文件本身, 而不显示其所指向的目录列表;
-f: 此参数的效果和同时指定“aU”参数相同, 并关闭“lst”参数的效果;
-i: 显示文件索引节点号(inode). 一个索引节点代表一个文件;
--file-type: 与“-F”选项的功能相同, 但是不显示“*”;
-k: 以 KB(千字节)为单位显示文件大小;
-l: 以长格式显示目录下的内容列表. 输出的信息从左到右依次包括文件名, 文件类型、权限模式、硬连接数、所有者、组、文件大小和文件的最后修改时间等;
-m: 用“,”号区隔每个文件和目录的名称;
-n: 以用户识别码和群组识别码替代其名称;
-r: 以文件名反序排列并输出目录内容列表;
-s: 显示文件和目录的大小, 以区块为单位;
-t: 用文件和目录的更改时间排序;
-L: 如果遇到性质为符号链接的文件或目录, 直接列出该链接所指向的原始文件或目录;
-R: 递归处理, 将指定目录下的所有文件及子目录一并处理;
--full-time: 列出完整的日期与时间;
--color[=WHEN]: 使用不同的颜色高亮显示不同类型的.
1
2
3
4
5
ls -sail
# 正则匹配 匹配一个字符
ls -l fileName?
# 匹配零个或多个字符
ls -l fileName*

简单工厂

简单工厂跟名字一样, 简单. 但是它却不属于设计模式中的一种, 只是因为软件开发中用的比较多, 而且够简单, 所以只是作为学习其他设计模式的例子,
慢慢深入.


没有使用设计模式的例子

设计一个计算器, 根据传入的操作符和待运算的数字, 得出结果.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Operation {
private double numberA;
private double numberB;
private String operateType;
public Operation(String operateType, double numberA, double numberB){
// 对参数进行检查操作
this.numberA = numberA;
this.numberB = numberB;
this.operateType = operateType;
}
public double getResult(){
double result = 0;
switch (operateType) {
case "+":
return numberA + numberB;
case "-":
return numberA - numberB;
case "*":
return numberA * numberB;
case "/":
if(numberB == 0) return 0;
return numberA / numberB;
}
return result;
}
}

根据输入的操作符做 switch 分支, 不同的操作符对应不同的计算, 最后返回结果.

缺点

  1. 构造方法中负责的太多操作, 造成代码冗长
  2. 客户端只能使用 new 关键字来创建操作类对象, 与客户端耦合较高, 对象的创建和使用没有分离.

重构

使用简单工厂进行重构.
抽象出一个操作类, 将都需要的属性封装到这个类中, 其他操作类继承这个操作类, 并且实现不同的操作.
然后使用简单工厂根据传入的操作类型创建不懂的操作类, 是创建和使用分离,
客户端只需要调用工厂类的工厂方法传入相应的参数即可得到一个具体的操作类进行操作.

简单工厂定义:

简单工厂模式 (Simple Factory Pattern): 定义一个工厂类, 它可以根据参数的不同返回不同类的实例, 被创建的实例通常都具有共同的父类.
因为在简单工厂模式中用于创建实例的方法是静态 (static) 方法, 因此简单工厂模式又被称为静态工厂方法 (Static Factory Method) 模式, 它属于类创建型模式.

UML

20241229154732_y9VrKljK.webp

代码实现

Operation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Operation {
private double numberA;
private double numberB;

public Operation(){

}

public double getNumberA() {
return numberA;
}

public void setNumberA(double numberA) {
this.numberA = numberA;
}

public double getNumberB() {
return numberB;
}

public void setNumberB(double numberB) {
this.numberB = numberB;
}

public double getResult() throws Exception {
return (double) 0;
}
}

具体操作类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class OperationAdd extends Operation{
@Override
public double getResult(){
return getNumberA()+ getNumberB();
}
}

class OperationSub extends Operation{
@Override
public double getResult(){
return getNumberA()- getNumberB();
}
}

class OperationMul extends Operation{
@Override
public double getResult(){
return getNumberA()* getNumberB();
}
}

class OperationDiv extends Operation{
@Override
public double getResult() throws Exception {
if(getNumberB() == 0){
throw new Exception("除数不能为0");
}
return getNumberA()/ getNumberB();
}
}

OperationFactory.java 简单工厂类, 负责创建具体操作类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OperationFactory {
public static Operation createOperation(String operationType) throws Exception {
switch (operationType){
case "+":
return new OperationAdd();
case "-":
return new OperationSub();
case "*":
return new OperationMul();
case "/":
return new OperationDiv();
default:
throw new Exception("操作不允许");
}
}
}

测试类使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class OperationTest {
/**
* Simple factory test.
* 使用简单工厂获取操作类对象进行计算
* @throws Exception the exception
*/
@Test
public void simpleFactoryTest() throws Exception {
com.dong4j.simple.Operation operation = OperationFactory.createOperation("+");
operation.setNumberA(101.0);
operation.setNumberB(10.0);
System.out.println(operation.getResult());

operation = OperationFactory.createOperation("-");
operation.setNumberA(-0.01);
operation.setNumberB(100.0);
System.out.println(operation.getResult());

operation = OperationFactory.createOperation("*");
operation.setNumberA(20);
operation.setNumberB(0.0);
System.out.println(operation.getResult());

operation = OperationFactory.createOperation("/");
operation.setNumberA(10.0);
operation.setNumberB(0.0);
System.out.println(operation.getResult());
}
}

优点

使用 Factory 来生产我们需要的具体操作类, 不需要在使用 new 来创建对象, 将创建过程和使用过程分开.
但是每次进行其他操作时, 还是需要传入具体操作类型获取对应的操作类对象, 即要修改客户端代码.
所以这里使用 properties 格式的配置文件, 将需要的参数写在配置文件中, 以后只需要修改配置文件即可 (也可以使用 xml)

步骤

添加 config.properties 文件

1
operationType=+

使用 ConfigUtil 读取配置文件

1
2
3
4
5
6
7
8
9
10
11
12
public class ConfigUtil {
public static String getOperationType(){
Properties properties = new Properties();
try {
// config.properties 必须放在 classpath 路径下才能加载.
properties.load(ConfigUtil.class.getClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
return properties.getProperty("operationType");
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OperationTest {
/**
* Config test.
* 使用配置工具类读取配置文件信息, 避免修改客户端代码
* @throws Exception the exception
*/
@Test
public void configTest() throws Exception {
// 使用配置文件获取操作符
String operationType = ConfigUtil.getOperationType();
com.dong4j.simple.Operation operation = OperationFactory.createOperation(operationType);
operation.setNumberA(101.0);
operation.setNumberB(10.0);
System.out.println(operation.getResult());
}
}

此时只需要修改配置文件即可获取不同操作类型.

简单工厂的简化

可以将静态工厂方法移动到 Operation 类中, 客户端执行通过父类的静态工厂方法, 根据参数的不同创建不同类型的子类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Operation {
private double numberA;
private double numberB;

public Operation(){

}

public double getNumberA() {
return numberA;
}

public void setNumberA(double numberA) {
this.numberA = numberA;
}

public double getNumberB() {
return numberB;
}

public void setNumberB(double numberB) {
this.numberB = numberB;
}

public double getResult() throws Exception {
return (double) 0;
}

// 再次优化, 将创建具体操作类的方法放在父类中, 不再使用简单工厂
public static Operation createOperation(String operationType) throws Exception {
switch (operationType){
case "+":
return new OperationAdd();
case "-":
return new OperationSub();
case "*":
return new OperationMul();
case "/":
return new OperationDiv();
default:
throw new Exception("操作不允许");
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OperationTest {
/**
* Operation static test.
* 将创建具体操作类的静态方法简化到 Operation 类中, 去掉 OperationFactory.
* @throws Exception the exception
*/
@Test
public void operationStaticTest() throws Exception {
// 使用配置文件获取操作符
String operationType = ConfigUtil.getOperationType();
com.dong4j.simple.Operation operation = com.dong4j.simple.Operation.createOperation(operationType);
operation.setNumberA(110.0);
operation.setNumberB(11.0);
System.out.println(operation.getResult());
}
}

总结

简单工厂模式专门提供了工厂类用于创建对象, 将对象的创建和对象的使用分开

优点:

  1. 工厂类负责创建具体产品类, 客户端可以不用创建对象, 直接使用即可.
  2. 客户端无需知道所创建具体产品名称, 只需要知道具体产品类所对应的参数即可, 减少了对于复杂类名的记忆.
  3. 通过引入配置文件, 可以不修改客户端的情况加更换具体产品类. 提高了系统灵活性

缺点:

  1. 使用简单工厂增加了类的个数, 是系统变得复杂, 不便于理解.
  2. 系统扩展困难, 如果需要新增操作类型, 将不得不修改工厂类的逻辑, 增加新的判断. 不利于系统的扩展和维护.

引用

代码下载

https://github.com/dong4j/pattern_code