1 定义:
工厂方法模式(Factory Method)
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.(定义一个用创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)
1.1 通用类图:
1.2 通用代码:
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。
- / * 抽象产品类 */
- public abstract class Product {
- //产品类的公共方法
- public void method1(){
- //业务逻辑处理
- }
- //抽象方法1
- public abstract void method2();
- }
- / * 具体的产品类 */
- public class ConcreteProduct1 extends Product {
- public void method2() {
- //业务逻辑处理
- }
- }
- / * 具体的产品类 */
- public class ConcreteProduct2 extendsProduct {
- public void method2() {
- //业务逻辑处理
- }
- }
- /*抽象创建者*/
- public abstract class Creator {
- /*
- * 创建一个产品对象,其输入参数类型可以自行设置
- * 通常为String、Enum、Class等,当然也可以为空
- */
- public abstract <T extends Product> T createProduct(Class<T> c);
- }
- /*具体创建者*/
- public class ConcreteCreator extends Creator {
- public<T extends Product> T createProduct(Class<T> c){
- Productproduct=null;
- try{
- product =(Product)Class.forName(c.getName()).newInstance();
- }catch (Exception e) {
- //异常处理
- }
- return(T)product;
- }
- }
- /*场景类*/
- public class Client {
- public static void main(String[] args) {
- Creatorcreator = new ConcreteCreator();
- Productproduct = creator.createProduct(ConcreteProduct1.class);
- /*
- * 继续业务处理
- */
- }
- }
备注 :此处对应产品类和创建类的 定义用的是abstract抽象父类的方式 , 但是其他 很多工厂方法模式中的实例中,对应产品类和创建类的定义 用的interface 接口的方式 。
2 优点
2.1 良好的封装性,代码结构清晰。
一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的具体过程,从而也降低了模块间的耦合。
2.2 扩展性非常优秀。
在增加产品类的情况下,只要适当修改具体的工厂类或扩展一个工厂类,就可以满足变化。
2.3 屏蔽产品类。
这一特点很重要,产品类的实现如何变化,调用者不需要关心,它只要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。(直接案例:JDBC连接数据库,更换数据库类型时只需要切换一下驱动名称)
2.4 工厂方法是典型的解耦框架。
高层模块只需要知道产品的抽象类,其他的实现类都不用关心。
符合迪米特法则,不需要的就不去交流;
符合依赖倒置原则,只依赖产品类的抽象;
符合里氏替换原则,使用产品子类替换产品父类。
3 缺点
暂无
4 应用场景
4.1工厂方法是创建一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重考虑是否要增加一个工厂类进行管理。
4.2 需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。
例如:需要设计一个连接邮件服务器的框架,有三种网络协议可以选择:POP3、IMAP、HTTP,我们可以把这三种连接方法作为产品类)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。
5 注意事项
5.1 使用不慎,可能会增加代码的复杂度。
6 扩展
6.1 缩小为简单工厂模式
如:一个模块仅需要一个工厂类,没有必要把它(抽象的创建者)产生出来,使用静态的方法就可以了,类图如下:
在实际项目中,采用该方法的案例还是较多的,其缺点是:简单模式下的工厂类扩展比较困难,不符合开闭原则,但很实用。
其中简单工厂模式在实际的实现过程中也存在1.普通工厂模式(单工厂单方法);2.多个工厂方式模式(单工厂多方法);3.静态工厂模式(方法为静态)等多种,其中静态工厂模式比较常用。
6.2 升级为多个工厂类
当在做一个比较复杂的项目时,会遇到初始化一个对象很耗费精力的情况,所有的产品类都放在一个工厂方法中进行初始化会使代码结构不清晰。
考虑到需要结构清晰,我们就为每个产品定义一个创建者,然后由调用者自己选择与哪个工厂方法关联(因为在实现工厂方法时,会具体到与对应的产品子类关联)。如下图
缺点:给可扩展性和可维护性带来了一定的影响。例如,如果要扩展一个产品类,就需要建立一个相应的工厂类,这样就增加了扩展的难度。因为工厂类和产品类的数量相同,维护时需要考虑两个对象之间的关系。
当然,在复杂的应用中一般采用多工厂的方法,然后再增加一个协调类,避免调用者与各个子工厂交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口。
6.3 替代单例模式
上一篇中介绍了单例模式,其核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象,类图如下:
Singleton定义了一个private的无参构造函数,目的是不充许通过new的方式创建一个对象,因而SingletonFactory只能通过反射来创建。当然不允许别人在别的地方以此方式创建此对象。(是否可以考虑:当项目中同时存在多个不同类的单例对象时,可以用此管理。)
- public class Singleton {
- //不允许通过new产生一个对象
- private Singleton(){
- }
- public void doSomething(){
- //业务处理
- }
- }
- public class SingletonFactory {
- private static Singleton singleton;
- static{
- try{
- Class cl= Class.forName(Singleton.class.getName());
- //获得无参构造
- Constructor constructor=cl.getDeclaredConstructor();
- //设置无参构造是可访问的
- constructor.setAccessible(true);
- //产生一个实例对象
- singleton= (Singleton)constructor.newInstance();
- }catch (Exception e) {
- //异常处理
- }
- }
- public static Singleton getSingleton(){
- return singleton;
- }
- }
- public class Client {
- public static void main(String[] args) {
- SingletonFactory.getSingleton().doSomething();
- }
- }
6.4 延迟初始化
一个对象被消费完毕后,并不立刻释放,工厂类保持其初始状态,等待再次被使用。延迟初始化是工厂方法模式的一个扩展应用,其通用类图如下所示:
- public class ProductFactory {
- private static final Map<String,Product> prMap = new HashMap();
- public static synchronized ProductcreateProduct(String type) throws Exception{
- Productproduct =null;
- //如果Map中已经有这个对象
- if(prMap.containsKey(type)){
- product= prMap.get(type);
- }else{
- if(type.equals("Product1")){
- product= new ConcreteProduct1();
- }else{
- product= new ConcreteProduct2();
- }
- //同时把对象放到缓存容器中
- prMap.put(type,product);
- }
- return product;
- }
- }
使用场景:在对象初始化比较复杂的情况下,便如硬件访问、涉及多方面的交互,则可以通过延迟加载降低对象的产生和销毁带来的复杂性。
它是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断Map中已有的对象数量来实现。(有点线程池的感觉)
例如JDBC连接数据库,都会要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量。
7 范例
华为生产手机:
源代码:
- package _02_FactoryMethod;
- public interface Phone {
- //手机类别
- public void getKind();
- //手机功能
- public void getFunctions();
- }
- public class FeaturePhone implements Phone{
- @Override
- public void getKind() {
- System.out.println("我是功能机! ");
- }
- @Override
- public void getFunctions() {
- System.out.println("我只有有限的功能!");
- }
- }
- public class SmartPhone implements Phone{
- @Override
- public void getKind() {
- System.out.println("我是智能机! ");
- }
- @Override
- public void getFunctions() {
- System.out.println("我有无限多的功能!");
- }
- }
- public abstract class AbstractPhoneFactory {
- public abstract <T extends Phone> T CreatePhone(Class<T> c);
- }
- public class PhoneFactory extends AbstractPhoneFactory {
- public <T extends Phone> T CreatePhone(Class<T> c){
- Phone phone = null;
- try {
- phone = (Phone)Class.forName(c.getName()).newInstance();
- } catch (Exception e) {
- System.out.println("手机制造错误!");
- }
- return (T) phone;
- }
- }
- public class Client {
- public static void main(String[] args) {
- AbstractPhoneFactory huawei = new PhoneFactory();
- //第一次,功能机
- System.out.println("2004年,华为生产第一台功能机!");
- Phone featurePhone = huawei.CreatePhone(FeaturePhone.class);
- featurePhone.getKind();
- featurePhone.getFunctions();
- //第二次,智能机
- System.out.println("2006年,华为生产第一台智能机!");
- Phone smartPhone = huawei.CreatePhone(SmartPhone.class);
- smartPhone.getKind();
- smartPhone.getFunctions();
- }
- }