前言
为什么有这种模式,为的是绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
结构图
具体实例(来源:菜鸟教程)简单工厂
这是一个简单工厂
//定义Product接口
public interface Shape {
void draw();
}
//具体实现Product
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
//工厂用于对象的创建
public class ShapeFactory {
//使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
//获取 Circle 的对象,并调用它的 draw 方法
Shape shape1 = shapeFactory.getShape("CIRCLE");
//调用 Circle 的 draw 方法
shape1.draw();
//获取 Rectangle 的对象,并调用它的 draw 方法
Shape shape2 = shapeFactory.getShape("RECTANGLE");
//调用 Rectangle 的 draw 方法
shape2.draw();
//获取 Square 的对象,并调用它的 draw 方法
Shape shape3 = shapeFactory.getShape("SQUARE");
//调用 Square 的 draw 方法
shape3.draw();
}
}
这里需要说明的是菜鸟教程提供的工厂模式更准确的说,这应该是简单工厂模式,简单工厂模式不属于23种设计模式中的一种。
工厂模式与简单工厂模式差别就在与对工厂的抽象,也就是结构图中的creater。
存在的困惑
如果我现在加了个菱形这样一个类,必然要对工厂(shapeFactory)里面的if else作出修改,这违反了开闭原则。于是我看见有网友提出采用反射,我一度的以为这种方法是正确的,其实不然。
先看一下采取反射的写法:
public class ShapeFactory {
public static Object getClass(Class<?extends Shape> clazz) {
Object obj = null;
try {
obj = Class.forName(clazz.getName()).newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obj;
}
}
主方法里面:
Rectangle rect = (Rectangle) ShapeFactory.getClass(Rectangle.class);
rect.draw();
Square square = (Square) ShapeFactory.getClass(Square.class);
square.draw();
这样确实,我新加的类不用去修改shapeFactory工厂类了,一定程度上满足了开闭原则。
这种方式有一个很大的弊端就是违反了迪米特法则(最少知道原则),也违反了工厂模式的初衷,工厂模式的存在就是为了让用户不用知道详细的实现细节,用户只需要知道,他需要一个什么就行了。然而用反射,因为反射是从类名反射而不能从类反射,也就是说用户要知道类名。这里感谢菜鸟教程的网友kusirp21。
至于使用枚举对工厂模式优化,暂时不做评价,等以后我的水平有所提升再做讨论,
工厂模式
public interface Logger{
public void writeLog();
}
public class DatabaseLogger implements Logger{
public void writeLog(){
System.out.println("数据库日志记录");
}
}
public class FileLogger implements Logger{
public void writeLog(){
System.out.println("文件日志记录");
}
}
//工厂接口
public interface LoggerFactory{
public Logger createLogger();
}
public class DatabaseLoggerFactory implements LoggerFactory{
public Logger createLogger(){
Logger logger = new DatabaseLogger();
return logger;
}
}
public class FileLoggerFactory implements LoggerFactory{
public Logger createLogger(){
Logger logger = new FileLogger();
return logger;
}
}
//Client客户端测试类
public class Client{
public static void main(String args[]){
LoggerFactory factory;
Logger logger;
factory = new FileLoggerFactory();
logger = factory.createLogger();
logger.writeLog();
}
}
如果需要增加并使用新的日志记录器,只需要对应增加一个新的具体工厂类,然后在客户端代码中修改具体工厂类名,原有类库的源代码无须做任何修改。
java设计模式(刘伟)一书提到,通过引入配置文件并使用反射机制可以实现不修改客户端代码的基础上更换具体工厂类,使其更加符合开闭原则,具有更好的灵活性和可扩展性。
反射机制与配置文件
在实际开发中可以对具体工厂类的实例化过程进行改进,在客户端代码中不直接使用new关键字来创建工厂对象,而是通过Java反射机制结合配置文件(例如XML文件)来生成具体工厂对象。在整个实现过程中用到两个技术,即Java反射机制与配置文件。
1.java反射机制
java反射:JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。又可以说为动态创建。
//通过类名生成实例对象并将其返回
Class c = Class.forName("java.lang.String");
Object obj = c.newInstance();
return obj;
2.配置文件
类名可以存储到XML配置文件中,再读取配置文件获取类名字符串,然后通过java反射机制来创建对象。以上面的FileLoggerFactory为例
<!-- config.xml -->
<?xml version = "1.0"?>
<config>
<className>类路径(包名.类名)</className>
</config>
为了读取该配置文件,并通过存储在其中的类名,字符串,反射生成对象,可以创建一个工具类XMLUtil,其详细代码如下:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
public class XMLUtil{
//该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
public static Object getBean(){
try{
//创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//designpatterns//factorymethod//config.xml"));
//获取包含类名的文本节点
NodeList nl=doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
}
客户端类:
public class Client{
public static void main(String args[]){
LoggerFactory factory;
Logger logger;
factory=(LoggerFactory)XMLUtil.getBean();
logger=factory.createLogger();
logger.writeLog();
}
}
总结:一句话概括工厂模式
简单工厂:一个工厂类,一个产品抽象类。
工厂方法:多个工厂类,一个产品抽象类。
抽象工厂:多个工厂类,多个产品抽象类。
参考文献
[1]https://www.runoob.com/design-pattern/factory-pattern.html
[2]Java设计模式 作者:刘伟