zoukankan      html  css  js  c++  java
  • 设计模式-挖掘工厂模式应用场景-全面分析工厂模式原理

    工厂模式的应用场景很多,到底再那些地方用过呢?为什么需要使用?有什么好处?本文将从应用场景出发逐步揭开工厂模式背后的面纱。

    先对工厂模式做一个大概的说明,其定义:

    Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)

    有了一个大概的概念后,我们来列举一下其应用场景:

    • Spring获取Bean,通过BeanFacotory工厂接口实现;
    • Log日志,通过LoggerFactory获取日志对象;
    • jdbc,创建数据库连接;

     Spring获取bean的代码如下:

    # 通过xml获取Bean
    ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-factoryMethod.xml");
    DemoApplication bean = ctx.getBean(DemoApplication.class);
    
    # 通过DI的方式注入Bean
    @Autowired
    DemoApplication demoApplication;
    
    # 通过DI的方式注入Bean
    @Service
    DemoApplication demoApplication;

    接下来我们看看ClassPathXmlApplicationContext是怎么实现的,先看到下面的类结构图:

    BeanFactory接口存在四种工厂SimpleJndiBeanFactory、HierachicalBeanFacory、AutowireCapleBeanFactory、ListableBeanFactory,这四个工厂也对应了获取Bean的不同方式比如通过Jndi获取bean就使用SimpleJndiBeanFactory工厂,通过xml获取bean就通过HierachicalBeanFacory工厂下的实现,通过依赖注入的方式就通过AutowireCapleBeanFactory工厂。

    Spring中有一个最大的功能就是DI(依赖注入),所依赖的对象一般都是接口,这也吻合了设计原则中的依赖倒置原则(DIP),而创建bean与之相关的也都交给了Spring容器来管理,也就是不需要关注bean的创建过程以及与之相关的其他对象,也就是设计原则中的迪米特法则(LOD),在这里我们所有的接口也可以使用其子类来进行替换,这也就吻合里氏替换原则(LSP)。

    Spring创建bean的使用的设计模式即为抽象工厂模式,需要实现不同创建bean方式只需要继承BeanFactory接口进行实现。

    抽象工厂定义:

    抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

    在最早学习Java的时候,JDBC获取数据库连接,实现代码如下:

    
    import java.sql.Connection;
    import java.sql.SQLException;
     
    public class ConnectionFactory {
     
    	public static Connection getConnection() {
    		Connection conn = null;
     
    			try {
    				conn = DataSourceHolder.getInstance().getDataSource()
    						.getConnection();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
     
    		return conn;
    	}
    
    }
    
    
    import javax.sql.DataSource;
    import org.apache.commons.dbcp.BasicDataSource;
     
     
    public class DataSourceHolder {
    	private BasicDataSource ds = new BasicDataSource();
     
    	private DataSourceHolder() {
    		ds.setDriverClassName("com.mysql.jdbc.Driver");
    		ds.setUrl("jdbc:mysql://localhost:3306/dbo");//dbo为连接的数据库名称
    		ds.setUsername("root");//账号
    		ds.setPassword("root");//密码
    	}
    	private static class SingletonHolder{
    		private static DataSourceHolder instance = new DataSourceHolder();
    	}
    	public static DataSourceHolder getInstance(){
    		return SingletonHolder.instance;
    	}
    	public DataSource getDataSource(){
    		return ds;
    	}
    
    }

    由于获取数据连接需要比较多的参数设置,其过程还是相对比较复制,因此使用ConnectionFactory数据库连接的工厂,非常简单的就可以获取数据库连接对象(使用ConnectionFactory .getConnection())。

    以上这种使用静态方法获取数据库连接的方式就是静态工厂模式,看到这种方式是不是可以想到我们之前实现的很多代码都非常相似,比如时间工具类、Json工具类,其实这些都归纳为静态工厂或者称之为简单工厂。

    简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

    接下来我们看看Logger的实现原理:

    获取logger的对象通常会通过以下方式获取

    org.slf4j.Logger logger = LoggerFactory.getLogger("DemoApplication");

    查询getLogger源码:

    public final class LoggerFactory {
    
        public static Logger getLogger(String name) {
            ILoggerFactory iLoggerFactory = getILoggerFactory();
            return iLoggerFactory.getLogger(name);
        }
    
    
        public static ILoggerFactory getILoggerFactory() {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                synchronized (LoggerFactory.class) {
                    if (INITIALIZATION_STATE == UNINITIALIZED) {
                        INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                        performInitialization();
                    }
                }
            }
            switch (INITIALIZATION_STATE) {
            case SUCCESSFUL_INITIALIZATION:
                return StaticLoggerBinder.getSingleton().getLoggerFactory();
            case NOP_FALLBACK_INITIALIZATION:
                return NOP_FALLBACK_FACTORY;
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
            case ONGOING_INITIALIZATION:
                // support re-entrant behavior.
                // See also http://jira.qos.ch/browse/SLF4J-97
                return SUBST_FACTORY;
            }
            throw new IllegalStateException("Unreachable code");
        }
    }

    很容易就可以判别出Logger使用了静态工厂,我们可以看到getILoggerFactory()方法,在这里根据logger不同配置参数创建了不同工厂,而这里所有的工厂都是实现了ILoggerFactory接口,因此在日志这个组件中也是用了抽象工厂,因此我们可以看出不管是Spring或者日志组件都使用了抽象工厂来增强其扩展性,这也就体现了抽象工厂最大的特性就是可扩展性强,也吻合开闭原则对扩展开发对修改关闭。

    接下来我们看看这样的一段代码:

    产品 

    public abstract class Product {      
         //产品类的公共方法
         public void method1(){
                 //业务逻辑处理
         }  
         //抽象方法
         public abstract void method2();    
    }
    
    public class ConcreteProduct1 extends Product {
         public void method2() {
                 //业务逻辑处理
         }
    }
    
    public class ConcreteProduct2 extends Product {
         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){
                 Product product=null;
                 try {
                        product = (Product)Class.forName(c.getName()).newInstance();
                 } catch (Exception e) {
                        //异常处理
                 }
                 return (T)product;         
         }
    }
    
    

    场景类

    public class Client {
         public static void main(String[] args) {
                 Creator creator = new ConcreteCreator();
                 Product product = creator.createProduct(ConcreteProduct1.class);
                 /*
                  * 继续业务处理
                  */
         }
    }

    以上是通过产品名称创建对应的产品的过程,这也是很多资料中介绍工厂模式时所使用的示例,这也就时工厂方法模式最基本的实现。

    总结:

    (1)简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory Method Pattern),一般使用静态方法进行实现。
    (2)工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式,Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。);
    (3)抽象工厂(Abstract Factory)模式,Provide an interface for creating families of related or dependent objects without specifying their concrete classes.(为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类。)。

    优点:

    1. 首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
    2. 其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。
    3. 再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
    4. 最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!

    使用场景:

    1. 首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
    2. 其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
    3. 再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
    4. 最后,可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。

    笔者的微信公众号,每天一篇好文章:

  • 相关阅读:
    UniEAP V4 开发实践说明文档
    SI_WorkShop_V4安装手册
    unieap platform eclipse.ini vm设置
    asp.net 配置 web.config 禁用VS2013自带的Browser Link功能
    unieap 建库
    onserverclick
    工作中记录的命令和知识点(不断更新)
    CentOS 下做端口映射/端口转发
    DELL服务器硬件信息采集SHELL脚本
    Linux中变量#,@,0,1,2,*,$$,$?的意思
  • 原文地址:https://www.cnblogs.com/coder306/p/13087590.html
Copyright © 2011-2022 走看看