工厂方法模式
工厂方法模式,定义一个接口用于创建对象的接口,让这个接口的子类决定实例化哪一个类。
分析定义
- 定义一个接口,用于创建对象。这个用于创建对象的接口,就是工厂接口AbstractFactory,接口定义了一个方法,用于返回一个类的实例
- 让这个接口的子类决定实例化哪一个类的实例。工厂接口的子类Factory,实现工厂接口的方法,这个方法调用某一个类的构造方法(对于更复杂的情况,不仅仅是用new调用构造函数那么简单,可能还会有其他的处理逻辑),然后将实例化好的对象返回。子类决定实例化哪个类,说明工厂会返回多个类的实例,至于返回哪一个,根据传入的参数来确定
举栗子——设计一个“生产”形状的工厂
public interface Shape{
void draw();
}
public class Circle implements Shape{
@Override
public void draw() {
System.out.println("圆形");
}
}
public class Square implements Shape{
@Override
public void draw() {
System.out.println("矩形");
}
}
public interface Factory{
Shape createShape(String shape);
}
public class ShapeFactory implements Factory {
/**
* 根据传入的Class类的对象,通过反射创建一个类的实例
*/
@Override
public Shape createShape(String shape) {
Shape s = null;
if(shape.equals("circle")){
s = new Circle();
} else if(shape.equals("square"){
s = new Square();
}
}
}
public class Client{
public static void main(String[] args) {
Factory factory = new ShapeFactory();
Shape shape = factory.createShape("circle");
shape.draw();
}
}
可以看到,我们创建一个圆形的对象时,仅仅通过一个字符串进行约束,工厂便根据约束字符串决定,创建了一个对象,我们甚至不知道这个圆形对象具体的类。
工厂方法模式的应用场景
大体上把工厂方法模式简单的介绍了一下,接下来就说说工厂方法模式在什么时候使用。
- 一个类不知道它所需要的对象的类:就像上面的例子,对于场景类Client来说,Shape的子类Circle是没必要被知道的,只要知道创建Circle类的实例的工厂方法就可以创建这个实例。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定。(可以将具体工厂的类名存储在配置文件或数据库中)
- 需要灵活的、可扩展的框架时,可以使用工厂方法模式。就拿熟悉的JDBC来举例子,其中有一个方法DriverManager.getConnection(String url),这就是一个工厂模式,传入不同的url,这个“连接工厂”就会返回不同版本数据库的连接,同样,不必要指导这个“连接产品”是哪一个类的实例。
工厂方法模式的优点
- 良好的封装性,代码结构清晰。一个对象的创建有条件约束,当创建一个对象时,仅需要指导“约束字符串”(就像JDBC中,数据库的URL就是约束字符串)或类名(有时候甚至不需要知道类名,在下面的“屏蔽产品类”中介绍)就可以创建一个对象,不必要知道创建这个对象的细节,降低了耦合。
- 扩展性非常优秀。如果增加了新的产品,只需要新建一个具体的产品类和一个具体的工厂类即可,对原来的代码没有修改或少量修改。(当然,这也会有新的问题,在下面的缺点中再介绍)
- 屏蔽产品类。这一点比较重要,产品类的实现如何变化,调用者不需要关心,只需要保证接口不发生变化,系统的上层模块就不需要变化。还拿JDBC举例子,如果系统更换数据库,只需要修改“约束字符串”,也就是驱动的名称即可。
- 工厂方法是典型的解耦框架。上层模块仅需要知道产品的抽象类(接口),其他的都不需要关心,符合迪米特法则,不需要的就不去交流;也符合依赖倒置原则,只依赖产品类的抽象;也符合里氏替换原则,产品的子类可以替代产品的父类。
工厂方法模式的缺点
在优点中说过,扩展时只需要添加两个类即可。在一定程度上增加了复杂度。