工厂模式: 工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。可分为简单工厂模式、工厂方法模式。以下分别对两种模式进行介绍。
简单工厂模式 (Simple Factory Pattern)
简单工厂模式,又称为静态工厂方法 (Static Factory Method) 模式,属于创建型模式,但不属于 23 种 GOF 设计模式之一。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,这个类就是工厂类,被创建的实例通常都具有共同的父类。下面用一个简单的计算器程序来介绍,计算器只包含加减乘除功能。
加减乘除四种操作类是运算类的子类,简单工厂类依赖于运算类。
运算类
package simple_factory;
//运算类
public class Operation {
private double num1;
private double num2;
public double getNum1() {
return num1;
}
public void setNum1(double num1) {
this.num1 = num1;
}
public double getNum2() {
return num2;
}
public void setNum2(double num2) {
this.num2 = num2;
}
//返回运算结果
public double getResult() {
return 0;
}
}
加法类
package simple_factory;
//加法类
public class Add extends Operation {
@Override
public double getResult() {
return this.getNum1() + this.getNum2();
}
}
减法类
package simple_factory;
//减法类
public class Sub extends Operation {
@Override
public double getResult() {
return this.getNum1() - this.getNum2();
}
}
乘法类
package simple_factory;
//乘法类
public class Mul extends Operation {
@Override
public double getResult() {
return this.getNum1() * this.getNum2();
}
}
除法类
package simple_factory;
//除法类
public class Div extends Operation {
@Override
public double getResult() {
return this.getNum1() / this.getNum2();
}
}
工厂类
package simple_factory;
//简单工厂类
public class OperationFactory {
public static Operation createOperation(String op) {
Operation operation = null;
switch (op) {
case "+":
operation = new Add();
break;
case "-":
operation = new Sub();
break;
case "*":
operation = new Mul();
break;
case "/":
operation = new Div();
break;
default:
break;
}
return operation;
}
}
客户端
package simple_factory;
//客户端
public class Client {
public static void main(String[] args) {
String op = "+";
Operation operation = OperationFactory.createOperation(op);
operation.setNum1(4);
operation.setNum2(5);
System.out.println(operation.getResult());;
}
}
优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责
- 客户端无需知道所创建具体产品的类名,只需知道具体产品类所对应的参数即可
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:
- 工厂类集中了所有产品的创建逻辑,职责过重,一旦异常,整个系统将受影响
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
- 简单工厂模式使用了static工厂方法,造成工厂角色无法形成基于继承的等级结构。
使用场景:
- 工厂类负责创建的对象比较少,所以不会造成工厂方法中的业务逻辑过于复杂
- 客户端只知道传入工厂类的参数,对如何创建对象不关心
总结: 简单工厂模式的优点在于工厂类中可以判断客户的的选择来动态实例化相关的类,对于客户端来说,去除了具体产品的依赖。但工厂类集中了对所有实例创建的逻辑,如果我们要新增子类或者改变方法的话,每次都得修改工厂类里面的代码,工厂类中的代码就会十分臃肿,这就等于说我们不仅开放了扩展,还开放了修改,这样就违反了 开放 - 封闭 原则。
工厂方法模式 (Factory Method Pattern)
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。与简单工厂模式相比,其提供了一个抽象工厂角色来声明抽象工厂方法(抽象工厂可以是接口,也可以是抽象类或者具体类),而由其子类来具体实现工厂方法,创建具体的产品对象,不同的具体工厂可以创建不同的具体产品。
工厂方法模式组成:
- 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
实例
某系统运行日志记录器 (Logger) 可以通过多种途径保存系统的运行日志,例如通过文件记录或者数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。
- Logger:日志记录器接口,充当抽象产品角色
package factory_method;
//日志记录器接口,充当抽象产品角色
public interface Logger {
public void writeLog();
}
- DatabaseLogger :数据库日志记录器,充当具体产品角色
package factory_method;
//数据库日志记录器,充当具体产品角色
public class DatabaseLogger implements Logger {
@Override
public void writeLog() {
System.out.println("数据库日志记录.");
}
}
- FileLogger:数据库日志记录器,充当具体产品角色
package factory_method;
//文件日志记录器,充当具体产品角色
public class FileLogger implements Logger {
@Override
public void writeLog() {
System.out.println("文件日志记录。");
}
}
- LoggerFactory :日志记录器工厂方法,充当抽象工厂角色
package factory_method;
//日志记录器工厂方法,充当抽象工厂角色
public interface LoggerFactory {
public Logger createLogger(); //抽象工厂方法
}
- DatabaseLoggerFactory :数据库日志记录器工厂类,充当具体工厂角色
package factory_method;
//数据库日志记录器工厂类,充当具体工厂角色
public class DatabaseLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new DatabaseLogger();
}
}
- FileLoggerFactory :文件日志记录器工厂类,充当具体角色
package factory_method;
//文件日志记录器工厂类,充当具体角色
public class FileLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new FileLogger();
}
}
- Client:客户端测试类
package factory_method;
//客户端测试类
public class Client {
public static void main(String[] args) {
//可引入配置文件和反射机制实现
LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.writeLog();
}
}
运行结果:
如果需要增加并使用新的日志记录器,只需要对应增加一个新的具体工厂类,然后再客户端代码中修改具体工厂类的类名,原有类库的源代码无需做任何修改。通过引入配置文件并使用反射机制可以实现在不修改客户端代码的基础上更换具体日志记录器。
优点
- 更符合开-闭原则,新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可。简单工厂模式需要修改工厂类的判断逻辑。
- 符合单一职责原则,每个具体工厂类只负责创建对应的产品。简单工厂中的工厂类存在复杂的switch逻辑判断。
- 不使用静态工厂方法,可以形成基于继承的等级结构。简单工厂模式的工厂类使用静态工厂方法。
- 工厂模式可以说是简单工厂模式的进一步抽象和拓展,在保留了简单工厂的封装优点的同时,让扩展变得简单,让继承变得可行,增加了多态性的体现。
缺点
- 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
- 一个具体工厂只能创建一种具体产品。
应用场景
- 当一个类不知道它所需要的对象的类时 ,在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
- 当一个类希望通过其子类来指定创建对象时 ,在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。