Java设计模式-工厂模式(Singleton Pattern)
目录
- 什么是工厂模式
- 三种工厂模式的实现
- JavaSE中工厂模式的使用
- Struts2工厂模式的应用
工厂模式完成了对象创建过程的封装,对外屏蔽实现过程
一、什么是工厂模式
1.1 什么是工厂模式?
工厂模式的出现是为了解决创建对象实例的问题,通常我们使用new关键字创建某个对象,但在特定的情况下,一个对象的创建需要一些列的步骤,依赖其它的模块对象,所以这类对象的创建**更像是一个过程**,而非单个动作。这与工厂的工作模式极为相似,工厂中制造一个汽车,比如要从全世界各地购买零部件,然后组装为一台汽车,而这个组装的过程,就是工厂模式的创建汽车的过程。
工厂模式利用了面向对象的封装特点,对外屏蔽实现过程。
二、3种工厂模式的实现
3.1 简单工厂模式(Simple Factory)
简单工厂模式完成了最原始、最简单的对象创建与业务逻辑的隔离,降低了耦合性,来看一个例子
package org.factory.simpleFactory;
/**
* 车的抽象类
*/
abstract class Car{
public Car(){
this.build();
}
public abstract void build();
}
class BMW extends Car {
public void build(){
System.out.println("生产一台BMW");
}
}
class Benz extends Car{
public void build(){
System.out.println("生产一台Benz");
}
}
/**
* 工厂类
*/
class CarFactory{
/**
* 创建汽车
* @param type 汽车类型
* @return 汽车实例
*/
public Car createCar(String type){
Car car = null;
if (type == "bmw"){
car = new BMW();
} else if (type == "benz") {
car = new Benz();
}
return car;
}
}
public class Client {
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
BMW bmw = (BMW) carFactory.createCar("bmw");
Benz benz = (Benz) carFactory.createCar("benz");
}
}
以上就是简单工厂模式的实现,有这么几个特点:
- 所有的对象都在一个工厂中创建
- 完成了对象创建功能和对象使用的隔离,封装了创建的过程
- 根据传递的参数即可创建不同对象,较为灵活
但很显然这种方式有很多问题
- 违反开闭原则,想要增加新对象,就要修改工厂类
- 扩展性差,if else太多,不够优雅,而且如果创建几百个类型的对象,工程类的维护会令人发指
但简单工厂模式也有自己的应用场景,例如在相对简单业务场景下,JDK中的Calendar类就使用了简单工厂模式。
3.2 工厂方法模式(Factory Method)
简单工厂的最大缺点在于工厂类不够灵活,工厂方法模式便将工厂进行抽象化,定义创建对象的接口,具体如何实现对象的创建,交给具体的工厂类。那么此时想要新增产品,只需要扩展实现抽象工厂类即可,符合开闭原则。
当使用者想要创建某个对象时,只需要找到对应的工厂,即可创建。工厂方法模式需要以下角色
- 抽象工厂 AbstractFactory: 定义创建对象的接口,具体由具体工厂实现
- 具体工厂 Factory:实现抽象工厂的接口,实现创建对象的过程
- 抽象产品 AbstractProduct:所有具体产品的父类,可以满足里氏替换的设计原则
- 具体产品 Product:工厂所建实例
下面为实例代码
package org.factory.factoryMethod;
/**
* 车的抽象类
*/
abstract class Car{
public Car(){
this.build();
}
public abstract void build();
}
class BMW extends Car {
public void build(){
System.out.println("生产一台BMW");
}
}
class Benz extends Car {
public void build(){
System.out.println("生产一台Benz");
}
}
/**
* 工厂的抽象类
*/
abstract class Factory{
public abstract Car create();
}
class BMWFactory extends Factory{
public BMW create() {
return new BMW();
}
}
class BenzFactory extends Factory{
public Benz create() {
return new Benz();
}
}
public class Client {
public static void main(String[] args) {
BenzFactory benzFactory = new BenzFactory();
benzFactory.create();
BMWFactory bmwFactory = new BMWFactory();
bmwFactory.create();
}
}
总结下上面的代码UML图
方法工厂有很多优点
- 扩展性强:方法工厂模式限定了某个工厂只能创建某个对象,只要知道工厂名字就可以知道他创建的对象是什么,而且扩展性极强,如果增加五菱汽车,只需要增加一个五菱工厂类和五菱汽车对象即可。
- 屏蔽产品:产品的变化与调用者无关,这有点像“中间层理念”,例如Java的JDBC连接,我们只需要更改驱动名称即可,其它的不需要关心,十分强大!
- 解耦:这个特性是所有的设计模式都有的优点,符合依赖倒置原则,值依赖产品类的抽象;也符合里氏替换原则,可以使用子类替换父类,符合迪米特法则,我只需要操作Factory,不操作触产品类方法。
方法工厂一般在抽象类的实现类会不断变化时会使用,例如信息通知功能,可以短信、可以是邮件、可以是抖音,在未来可能是其它的方式
3.3 抽象工厂模式(Abstract Factory)
抽象模式是最难理解的模式,首先,观察下方法工厂模式,有没有发现方法工厂模式中只用到了一种“产品”?需要Create,直接就创建了一个对象,但我们回想下工厂制造汽车的场景,更多的是在“组装”。例如创建一个汽车需要:轮胎、天窗、底盘等,而这三个都是单独的对象。
这就很明确了:我生成一个宝马需要天窗、底盘等产品,接下来通过代码实现上面的需求(也可以先看代码后面的UML图,再回头看代码,会更高效)
package org.factory.abstractFactory;
/**
* 轮胎的抽象类
*/
abstract class Tyre{
public Tyre(){
this.getName();
this.getColor();
}
public abstract void getColor();
public abstract void getName();
}
class TyreA extends Tyre {
public void getColor() {
System.out.println("轮胎A的颜色是黑色");
}
public void getName() {
System.out.println("轮胎A的名字是轮胎A");
}
}
class TyreB extends Tyre {
public void getColor() {
System.out.println("轮胎B的颜色是红色");
}
public void getName() {
System.out.println("轮胎B的名字是轮胎B");
}
}
/**
* 天窗的抽象类
*/
abstract class Window{
public Window(){
this.open();
this.close();
}
public abstract void open();
public abstract void close();
}
class WindowA extends Window {
public void open() {
System.out.println("WindowA会开窗");
}
public void close() {
System.out.println("WindowA会关窗");
}
}
class WindowB extends Window {
public void open() {
System.out.println("WindowB会开窗");
}
public void close() {
System.out.println("WindowB会关窗");
}
}
/**
* 汽车的抽象类
* 每个汽车都需要安装Window和Tyre
*/
abstract class Car {
public abstract void setWindow(Window window);
public abstract void setTyre(Tyre tyre);
}
class BMW extends Car {
private Window window;
private Tyre tyre;
public void setWindow(Window window) {
this.window = window;
}
public void setTyre(Tyre tyre) {
this.tyre = tyre;
}
}
class Benz extends Car {
private Window window;
private Tyre tyre;
public void setWindow(Window window) {
this.window = window;
}
public void setTyre(Tyre tyre) {
this.tyre = tyre;
}
}
/**
* 工厂的抽象类
*/
interface Factory{
public Window createWindow();
public Tyre createTyre();
public Car create();
}
class BMWFactory implements Factory {
public BMW create() {
System.out.println("开始生产BMW");
BMW bmw = new BMW();
bmw.setTyre(createTyre());
bmw.setWindow(createWindow());
return bmw;
}
public Window createWindow() {
return new WindowB();
}
public Tyre createTyre() {
return new TyreA();
}
}
class BenzFactory implements Factory {
public Benz create() {
System.out.println("开始生产Benz");
Benz benz = new Benz();
benz.setTyre(createTyre());
benz.setWindow(createWindow());
return benz;
}
public Window createWindow() {
return new WindowB();
}
public Tyre createTyre() {
return new TyreB();
}
}
public class Client {
public static void main(String[] args) {
BMWFactory bmwFactory = new BMWFactory();
bmwFactory.create();
BenzFactory benzFactory = new BenzFactory();
benzFactory.create();
}
}
// 运行结果如下
开始生产BMW
轮胎A的名字是轮胎A
轮胎A的颜色是黑色
WindowB会开窗
WindowB会关窗
开始生产Benz
轮胎B的名字是轮胎B
轮胎B的颜色是红色
WindowB会开窗
WindowB会关窗
代码很多,表达出了抽象工厂的使用方法,抽象工厂有很多优点
- 封装性:Factory屏蔽了技术细节,使得高层模块只关心抽象和接口即可,工厂负责创建实例
- 产品族内的约束为非公开状态:高层模块只知道工厂会返回我一个宝马,但宝马内部用的什么材料我不清楚,但你给我一个宝马实例即可,具体的产品族内的约束是在工厂内实现的
缺点也很明显,产品(宝马)的扩展很困难,如果在宝马中增加后备箱,想一下需要改哪些地方?
- 首先Factory接口、BMWFactory肯定要修改,因为要增加createHouBX方法
- Car接口以及实现类都要增加setHouBX的方法
- 增加HouBX类和实现类(这是没什么问题的)
所以主要问题是要修改Factory/Car接口,这是违反开闭原则的。所有实现Factory/Car的类都要修改、测试。
总结下:
- 在产品中增加零部件是困难的(例如宝马增加后备箱)
- 增加产品的种类是容易得(增加五菱汽车)
- 抽象模式比作数据库连接切换的话,可以理解为虽然便于两数据库之间的切换,但是不便于增加需求功能。
对比下三种工厂模式
- 简单工厂:只有唯一工厂(简单工厂),一个产品接口/抽象类,根据简单工厂中的静态方法来创建具体产品对象。适用于产品较少,几乎不扩展的情景
- 工厂方法:有多个工厂(抽象工厂+多个具体工厂),一个产品接口/抽象类,根据继承抽象工厂中的方法来多态创建具体产品对象。适用于一个类型的多个产品
- 抽象方法:有多个工厂(抽象工厂+多个具体工厂),多个产品接口/抽象类,对产品子类进行分组,根据继承抽象工厂中的方法多态创建同组的不同具体产品对象。适用于多个类型的多个产品,例如BenzFactory在创建汽车时,可以使用WindowA也可以使用WindowB。
三、JavaSE中工厂模式的使用
首先列举下使用工厂模式的类(看到名为newInstance的方法,大概率就是工厂模式)
- java.util.Calendar 类 getInstance 方法使用了简单工厂
- java.lang.Class 类的 newInstance 方法
- java.lang.Class 类的 forName 方法
- java.lang.reflect.Array 类的 newInstance 方法
- java.lang.reflect.Constructor 类的 newInstance 方法
- java.lang.reflect.Proxy 类的 newProxyInstance 方法
- JDK 中8 种基本类型的包装类 Integer、Long、Short、Character、Byte、Float、Double、Boolean 的 valueOf 方法使用了简单工厂,可以根据参数创建不同的对象
java.util.Calendar 类 getInstance 方法使用了简单工厂
类中有非常多的getInstance方法的重载,用于创建实例
getInstance方法中会调用createCaledar方法去创建不同的日期计算的类
java.lang.Class 类的 newInstance 方法
Class类调用构造器创建对象实例
java.lang.Class 类的 forName 方法
forName方法会调用forName0方法,该方法是一个被native修饰的方法。
native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
native 是用做java 和其他语言(如c++)进行协作时使用的,也就是native 后的函数的实现不是用java写的。
既然都不是java,那就别管它的源代码了,我们只需要知道这个方法已经被实现即可。
native的意思就是通知操作系统, 这个函数你必须给我实现,因为我要使用。 所以native关键字的函数都是操作系统实现的, java只能调用。
java.lang.reflect.Array 类的 newInstance 方法
java.lang.reflect包下除了提供Method(方法)、Constructor(构造器)、Filed(成员变量)这三个类,这三个类使我们在学习反射时常用的类,除此之外还提供了Array类,Array对象可以代表所有的数组,可通过Array类来动态创建数组,newInstance用于创建指定元素类型、指定维度的数组。这里的dimensions是可变个数的。
该方法是一个native方法
8种基本类型的valueOf方法
通过valueOf方法,new出实例,其它的基本类型不在一一讲解
java.lang.reflect.Constructor 类的 newInstance 方法
通过构造器创建对象实例
java.lang.reflect.Proxy 类的 newProxyInstance 方法
通过构造器创建对象实例
四、Struts2工厂模式的应用
4.1 InternalFactory类
在Struts2的依赖注入中使用了工厂模式,关于Struts2的容器加载机制,可以浏览《Mark链接-Struts2依赖注入实现原理》
Struts2在依赖注入时定义了InternalFactory接口
// 内部工厂接口
interface InternalFactory<T> extends Serializable {
/**
* Creates an object to be injected.
*
* @param context of this injection
* @return instance to be injected
*/
T create(InternalContext context);
}
在ContainerBuilder类将InternalFactory加入factories列表时,生成内部类实现create方法,create方法中定义了一个对象创建的过程,一个比较简单的工厂模式。
4.2 ObjectFactory类
该类无父类接口,就是一个实现类,完成对Struts2的Action、Bean、Result、Interceptor对象的创建,也是一个比较简单的工厂模式