0.参考
工厂模式披萨案例详解
简单工厂模式、工厂方法模式和抽象工厂模式的区别. - 阿里巴巴淘系技术的回答 - 知乎(推荐)
尚硅谷韩顺平-图解设计模式-工厂模式, 和如上阿里的知乎回答有较大出入, 推荐后者.
1.基本介绍
1.1概述
- 在一些情况下,要创建的对象需要一系列复杂的初始化操作,比如查配置文件、查数据库表、初始化成员对象等,如果把这些逻辑放在构造函数中,会极大影响代码的可读性。不妨定义一个类来专门负责对象的创建,这样的类就是工厂类,这种做法就是工厂模式,在任何需要生成复杂对象的地方,都可以使用工厂模式。
- 工厂模式包括:
- 简单工厂:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。(不在23种设计模式内, 属于一种普通的编码方式)
- 工厂方法:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。
- 抽象工厂:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了工厂子类的数量。
1.2引出
- 客户端在调用时不想判断来实例化哪一个类或者实例化的过程过于复杂。
- 在工厂模式中,具体的实现类创建过程对客户端是透明的,客户端不决定具体实例化哪一个类,而是交由“工厂”来实例化。
1.3使用场合
- 在编码时不能预见需要创建哪种类的实例。
- 系统不应依赖于产品类实例如何被创建、组合和表达的细节。
- 工厂模式就是为了方便创建同一接口定义的具有复杂参数和初始化步骤的不同对象。工厂模式一般用来创建复杂对象。只需用new就可以创建成功的简单对象,无需使用工厂模式,否则会增加系统的复杂度。
- 此外,如果对象的参数是不固定的,推荐使用Builder模式。
- <br />
2.案例
2.1具体需求:
看一个披萨的项目:要便于披萨种类的扩展,要便于维护。
- 披萨的种类很多(比如GreekPizza、CheesePizza等);
- 披萨的制作有prepare,bake,cut,box;
-
2.2传统方式解决
2.2.1方案:
- (在如下类结构上)编写 OrderPizza类 直接调用 Pizza类 以完成订购业务
```java
public class OrderPizza {
//构造方法
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购披萨的类型
do {
orderType = getType();
if (orderType.equals(“greek”)) {
} else if (orderType.equals(“cheess”)) {pizza = new GreekPizza();
pizza.setName("GreekPizza");
} else {pizza = new CheessPizza();
pizza.setName("CheessPizza");
} //输出pizza制作过程 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } while (true); }break;
- (在如下类结构上)编写 OrderPizza类 直接调用 Pizza类 以完成订购业务
```java
public class OrderPizza {
//构造方法
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购披萨的类型
do {
orderType = getType();
if (orderType.equals(“greek”)) {
//写一个方法,可以获取客户希望订购的披萨种类 private String getType() { try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
} } } ```
- 
2.2.2优缺点:
- 优点: 容易理解,简单易操作
- 缺点:
- 违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽量少修改代码;
- 比如这时我们要新增加一个Pizza的种类(Pepper披萨),我们需要做的修改较大。(需要对所有的 引用了Pizza类 的 OrderPizza 进行代码修改)
- 
- 
2.2.3改进方式:
- **把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时,只需要修改该类就可,其它有创建到Pizza对象的代码就不需要修改了** --> 简单工厂模式。
3.使用模式
3.1简单(静态)工厂模式
3.1.1概述
1. 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
1. 定义了一个创建对象的类,由这个类来封装实例化对象的行为。
1. 在软件开发中,当我们会用到大量的创建某种、某类或者谋批对象时,就会使用到工厂模式。
3.1.2方案
- 
public class SimpleFactory {
//简单工厂模式,也叫静态工厂模式,可以用static修饰该方法
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("GreekPizza");
} else if (orderType.equals("cheess")) {
pizza = new CheessPizza();
pizza.setName("CheessPizza");
}
//输出pizza制作过程
pizza.prepare();c
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
3.1.3优缺点
- 优点 :
- 一个简单的工厂类就能够完成一个简单的创建不同子类的简单业务
- 缺点:
- 上面的工厂实现是一个具体的类SimpleFactory,而非接口或者抽象类,createPizza()方法利用if-else创建并返回具体的Pizza实例,如果增加新的Pizza子类,该披萨工厂的创建方法中就要增加新的if-else。这种做法扩展性差,违背了开闭原则,也影响了可读性。所以,这种方式使用在业务较简单,工厂类不会经常更改的情况。
- 对于业务复杂化后, 仅靠简单工厂会难以维护和扩展.
- 如本案例: [:客户在点披萨时,可以点不同口味的披萨,比如“北京的奶酪披萨”、“北京的胡椒披萨”或者是“伦敦的奶酪披萨”、“伦敦的胡椒披萨”.使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzzaSimpleFactory等等,从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性 、可扩展性并不是特别好。]. --> 工厂方法模式
3.2工厂方法模式
3.2.1概述
1. 使用工厂方法模式。将(披萨项目实例化)功能抽象成抽象方法,在不同行为的(口味点餐)子类中具体实现。
1. 工厂方法模式定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
1. 为了解决上面提到的"增加if-else"的问题,可以为每一个Pizza子类建立一个对应的工厂子类(在该方案中体现为 XxxOrderPizza类),这些工厂子类实现同一个抽象工厂接口。这样,创建不同品牌的键盘,只需要实现不同的工厂子类。当有新品牌加入时,新建具体工厂继承抽象工厂,而不用修改任何一个类。
3.2.2方案
- //抽象的OrderPizza.createPizza() 并未实现, 而是由具体子类区实现该方法.
// 抽象父类及其待具体子类实现的抽象行为: createPizza()
public abstract class OrderPizza {
//构造方法
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购披萨的类型
do {
orderType = getType();
//调用方法
pizza = creatPizza(orderType);
//输出pizza制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
//定义一个抽象方法,让各个工厂子类自己实现
abstract Pizza creatPizza(String orderType);
//写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
3.2.3优缺点
- 优点
- 客户端不需要在负责对象的创建,明确了各个类的职责
- 如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可
- 不会影响已有的代码,后期维护容易,增强系统的扩展性
- 缺点
- 每一种品牌对应一个工厂子类,在创建具体键盘对象时,实例化不同的工厂子类。但是,如果业务涉及的子类越来越多,难道每一个子类都要对应一个工厂类吗?这样会使得系统中类的个数成倍增加,增加了代码的复杂度。 --> 抽象工厂模式
3.3抽象工厂模式
3.3.1概述
1. 定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
2. 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者 称为进一步的抽象)。将工厂抽象成两层,AbsFactory(抽象工厂)+ 具体的工厂子类。
2. 可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护。
2. 为了缩减工厂实现子类的数量,不必给每一个产品分配一个工厂类,可以将产品进行分组,每组中的不同产品由同一个工厂类的不同方法来创建。
2. 例如,键盘、主机这2种产品可以分到同一个分组——电脑,而不同品牌的电脑由不同的制造商工厂来创建。类似这种把产品类分组,组内不同产品由同一工厂类的不同方法实现的设计模式,就是抽象工厂模式。
3.3.2方案
- 
//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {
//让下面的工厂子类来具体实现
Pizza createPizza(String orderType);
}
//具体的工厂实现子类
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("Cheess")) {
pizza = new BJCheessPizza();
}
if (orderType.equals("Pepper")) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
// 依赖(抽象)工厂的OrderPizza客户端
public class OrderPizza {
AbsFactory factory;
//构造器
public OrderPizza(AbsFactory factory) {
setFactory(factory);
}
private void setFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = "";//用户输入
this.factory = factory;
do {
orderType = getType();
//factory可能是北京的工厂子类,也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.box();
pizza.cut();
} else {
System.out.println("订购失败");
break;
}
} while (true);
}
//写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
3.3.3优缺点
- 优点
- 为了缩减工厂实现子类的数量,不必给每一个产品分配一个工厂类,可以将产品进行分组,每组中的不同产品由同一个工厂类的不同方法来创建。
- 缺点
- 增加分组非常简单,例如要增加Lenovo分组,只需创建Lenovo工厂和具体的产品实现类。分组中的产品扩展非常困难,要增加一个鼠标Mouse,既要创建抽象的Mouse接口, 又要增加具体的实现:DellMouse、HPMouse, 还要再每个Factory中定义创建鼠标的方法实现。
3.3.4适用情况
1. 一个系统要独立于它的产品的创建、组合和表示时;
1. 一个系统要由多个产品系列中的一个来配置时;
1. 要强调一系列相关的产品对象的设计以便进行联合使用时;
1. 当你提供一个产品类库,而只想显示它们的接口而不是实现时;
4.经典使用
4.1 Spring中
- Spring中的InitializingBean接口(BeanFactory子接口),可以利用@Autowired注解优雅的实现工厂。
4.2 JDK中
- Calendar.newInstance(): Calendar
- 
4.3 Redis集群扩展接口
- 随着业务发展, 从单机Redis到负载更强的Redis集群.
- 多服务使用Redis, 需要一起升级到集群
- 需要兼容集群, 方便后续的灾备, 及时切换集群
- 集群之间提供的接口/方法各有差异, 需要进行适配
4.4 不同操作系统中的单行结尾字符
- Unix: <换行>, \n
- windows: <换行><回车>, \n\r
- Mac: <回车>, \r
5.小结
5.1工厂模式的意义
- 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到与主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
5.2设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
- 不要覆盖基类中已经实现的方法。