热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何基于Spring使用工厂模式实现程序解耦

这篇文章主要介绍了如何基于Spring使用工厂模式实现程序解耦,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

这篇文章主要介绍了如何基于Spring使用工厂模式实现程序解耦,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

1、 啥是耦合、解耦?

既然是程序解耦,那我们必须要先知道啥是耦合,耦合简单来说就是程序的依赖关系,而依赖关系则主要包括

1、 类之间的依赖

2、 方法间的依赖

比如下面这段代码:

 public class A{
    public int i;
  }

  public class B{
    public void put(A a){
      System.out.println(a.i);
    }
  }


上面这个例子中A类和B类之间存在一种强耦合关系,B类直接依赖A类,B类的put方法非A类类型不可,我们把这种情况叫做强耦合关系。

实际开发中应该做到:编译期不依赖,运行时才依赖。怎么理解呢?我们很容易想到多态向上转型,是的,编译时不确定,运行时才确定,当然接触面更广一点的童鞋会想到接口回调,是的接口回调方式也能有效的解耦!如下代码:

//一个接口叫做Inter,里面定义了一个happy()方法,有两个类A、B实现了这个接口

interface Inter{
  void happy();
}

class A implements Inter{

  @Override
  public void happy() {
    System.out.println("happy...A");
  }
}

class B implements Inter{

  @Override
  public void happy() {
    System.out.println("happy...B");
  }
}

public class Test{
  public void happys(Inter inter){
    inter.happy();
  }
}

是的,如上代码正是典型的接口回调,Test类中的happys方法参数变的相对灵活起来,代码中Test类与A类、B类之间就存在一种弱耦合关系,Test类的happys方法的参数可以使A类类型也可以是B类类型,不像强耦合关系中非A类类型不可的情形。

从某一意义上来讲使用类的向上转型或接口回调的方式进行解耦都是利用多态的思想!

当然解耦的方式还有很多,从根本意义上讲实现低耦合就是对两类之间进行解耦,解除类之间的直接关系,将直接关系转换成间接关系,从而也有很多设计模式也对程序进行解耦,比如:适配器模式、观察者模式、工厂模式....总之,必须明确一点:耦合性强的程序独立性很差!

2、 jdbc程序进行解耦

先来看一段代码:

//1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //如果把jdbc的MySQLjar包依赖去除直接编译失败提示没有mysql  
//2、获取连接
Connection cOnn=DriverManager.getConnection("jdbc:mysql://localhost:3306/ufida","root","root");
//3、获取操作数据库的预处理对象
PreparedStatement pstm=conn.prepareStatement("select * from client");
//4、执行SQL,得到结果集
ResultSet rs=pstm.executeQuery();
//5\遍历结果集
while(rs.next()){
  System.out.println(rs.getString("name"));
}
//6、释放资源
rs.close();
pstm.close();
conn.close();

等等等等,好熟悉好怀念的代码.....

没错就是jdbc的代码,不是用来怀旧的,而是如果这样设计,你会觉得这样的程序耦合性如何?又如何进行解耦?先仔细思考一番。

一分钟过去了.....

两分钟过去了.....

好了,我们都知道jdbc连接MySQL需要一个mysql-connector的jar包,如果我们把这个jar包依赖或者这个jar包给去掉,显然上面的这个程序会编译报错,如下图


显然这样的程序耦合性过高!于是我们可以这样设计,将第一步的注册驱动代码new的方式改成反射的方式如下:

 //1、new的方式注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //如果把jdbc的MySQLjar包依赖去除直接编译失败提示没有mysql相关的jar包

改为如下方式

 //2、反射的方式注册驱动
Class.forName("com.mysql.jdbc.Driver"); //改用这种方式注册驱动会发现不会编译失败,相比上面的方式相对解耦,但是依然存在缺陷:若连接改为Oracle数据库,这里的字符串又要进行改动!

正如注释的解释一样,又一个缺陷就浮现了:若连接改为Oracle数据库,这里的字符串又要进行改动!

于是对于这个jdbc程序来说就有这样的一个解耦思路:

第一步:通过反射来创建对象,尽量避免使用new关键字

第二步:通过读取配置文件来获取创建的对象全限定类名

3、传统dao、service、controller的程序耦合性

顺着jdbc程序的解耦思路,我们再来看看传统dao、service、controller的程序耦合性分析

由于只是一个demo,省去dao层的操作.....

定义一个Service接口

public interface IAccountOldService{
  public void save();
}

Service接口实现类

public class AccountServiceOldImpl implements IAccountOldService{
  @Override
  public void save() {
    System.out.println("save成功一个账户....");
  }
}

controller代码:

public class AccountCencollertOld {
  public static void main(String[] args) {
    IAccountOldService iaccount=new AccountServiceOldImpl (); 
    iaccount.save(); //运行结果:save成功一个账户....
  }
}

到这里,有何想法?表面上来看是没有一点问题的,So Beautiful,但仔细的看。表现层与业务层、业务层与持久层紧紧的互相依赖关联,这与我们开发程序的高内聚低耦合原则相违背,哦My God,So Bad!我们顺着jdbc程序的解耦思路,我们应该尽量避免使用new关键字,我们发现这些层里面service层new 持久层dao,controller表现层new 业务层service....太糟糕了

那么对此,你有何解耦思路?

4、使用工厂模式实现解耦

别想了,工厂模式实现程序解耦你值得拥有!顺着jdbc程序的解耦思路:

1、通过读取配置文件来获取创建的对象全限定类名
2、通过反射来创建对象,尽量避免使用new关键字

首先在resources目录下中写一个bean.properties配置类,具体内容如下

accountServiceOld=com.factory.service.impl.AccountServiceOldImpl

接着使用工厂方法代码:

/**
 * 一个创建Bean对象的工厂
 *
 *  1、需要一个配置文件来配置我们的service和dao  配置文件的内容:唯一标识=全限定类名(key-value)
 *  2、通过读取配置文件中配置的内容,反射创建对象
 *
 *  场景:主要是service调用dao,controller调用service的程序。这里面耦合性非常的高,互相new互相依赖
 *
 *  为了解耦,利用工厂模式进行
 */
 public class BeanFactoryOld {
  private static Properties props;

  static{
    try {
      //实例化对象
      props = new Properties();

      //获取properties文件的流对象
      InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
      props.load(in);//加载其对应路径下的配置文件

    }catch (Exception e){
      throw new ExceptionInInitializerError("初始化properties失败!");
    }
  }

  //根据bean的名称获取bean对象
  public static Object getBean(String beanName){
    Object bean=null;
    try {
    String beanPath= props.getProperty(beanName);
    bean = Class.forName(beanPath).newInstance();  //这里的newInstance创建实例(默认无参构造器)每次执行都需要创建一次
    } catch (Exception e) {
      e.printStackTrace();
    }
    return bean;
  }
}

此时,controller的代码就可以编写为

/**
 * 这里模拟一个controller调用service
 *
 */
public class AccountCencollertOld {
  public static void main(String[] args) {
  //  IAccountOldService iaccount=new AccountServiceOldImpl (); //使用工厂方法不再通过new方式

    IAccountOldService iaccount= (IAccountOldService) BeanFactoryOld.getBean("accountServiceOld");
    iaccount.save(); //运行结果:save成功一个账户....  说明成功调用了service
  }
}

通过运行结果,属实没毛病,成功降低程序耦合!So Beautiful!先高兴一会吧,因为马上出现.....但是,随之而来的问题又出现了,我们对这个controller进行一下改写

for(int i=0;i<5;i++){
    IAccountOldService iaccount= (IAccountOldService) BeanFactoryOld.getBean("accountServiceOld");
    iaccount.save(); 
   }

打印结果:

com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
com.factory.service.impl.AccountServiceImpl@677327b6
save成功一个账户....
com.factory.service.impl.AccountServiceImpl@14ae5a5
save成功一个账户....
com.factory.service.impl.AccountServiceImpl@7f31245a
save成功一个账户....
com.factory.service.impl.AccountServiceImpl@6d6f6e28
save成功一个账户....

打印的是五个不同的对象,说明是多例的,每次调用getBean的时候都会newInstance出一个新对象,如下


多例每次都要创建对象,资源浪费、效率低下

针对单例多例情况,我们再对service业务层代码进行修改:

public class AccountServiceImpl implements IAccountService {
  //定义类成员
  private int i=1; 

  @Override
  public void save() {
    System.out.println("save成功一个账户....");
    System.out.println(i);
    i++;
  }
}

运行controller代码,运行结果

save成功一个账户....
1
save成功一个账户....
1
save成功一个账户....
1
save成功一个账户....
1
save成功一个账户....
1

why?多例因为每次都是新的对象,上面也验证过了,因此每次创建新对象都会初始化一次,重新赋值,所以都是1,如果我们把类成员改为局部成员变量如下

public class AccountServiceOldImpl implements IAccountOldService {

//  private int i=1; 
  @Override
  public void save() {
    int i=1;  //改为局部变量
    System.out.println("save成功一个账户....");
    System.out.println(i);
    i++;
  }
}

不用猜,运行结果同样是1。算了还是运行一下吧哈哈哈

save成功一个账户....
1
save成功一个账户....
1
save成功一个账户....
1
save成功一个账户....
1
save成功一个账户....
1

说了这么多,通过观察service和dao之间单不单例好像无所谓,因为他们之间并没有业务方法中改变的类成员,所以并不需要多例来保证线程安全。那说这些有何意义?不要忘了,由于使用了工厂改进如下中的.newInstance创建实例(默认无参构造器)每次执行都需要创建一次,这样就不好了(浪费资源),因此我们要设计出只newInstance创建一次实例就很完美了,这也是我为啥要在service和controller中都添加一个Old关键字的原因了,接下来我们来看看工厂是如何改进的!

5、工厂模式改进

为了不被搞晕,我们重新写代码,也就是重头开始写代码~其实就是把Old去掉~

由于只是一个demo,省去dao层的操作.....

定义一个Service接口

public interface IAccountService {
  public void save();
}

Service接口实现类

public class AccountServiceImpl implements IAccountService{

  @Override
  public void save() {
    System.out.println("save成功一个账户....");
  }
}

controller代码:

/**
 * 这里模拟一个controller调用service
 *
 */
public class AccountCencollert {
  public static void main(String[] args) {
//    IAccountService iaccount=new AccountServiceImpl(); 

    IAccountService iaccount= (IAccountService) BeanFactory.getBean("accountService");
    iaccount.save(); //运行结果:save成功一个账户....  说明了成功调用了service
   }
 }

改进的工厂方法代码:

public class BeanFactory {
  private static Properties props;

  //定义一个map容器,用于存放创建的对象
  private static Map beans; //改进的代码============

  static{
    try {
      //实例化对象
      props = new Properties();

      //获取properties文件的流对象
      InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
      props.load(in);//加载其对应路径下的配置文件

      ////////////////////以下是改进的代码=======================
      //实例化容器
      beans=new HashMap();
      //取出配置文件中所有的key值
      Enumeration keys = props.keys();
      //遍历枚举
      while(keys.hasMoreElements()){
        //取出每个key
        String key = keys.nextElement().toString();
        //根据key取出对应的value (这里因为每个value值对应着类路径)
        String beanPath = props.getProperty(key);
        //反射创建对象
        Object value = Class.forName(beanPath).newInstance();
        //把key和value存入容器中
        beans.put(key,value);
      }
    }catch (Exception e){
      throw new ExceptionInInitializerError("初始化properties失败!");
    }
  }


  //随着代码的改进,我们就可以简化下面的获取bean对象的方法,如下代码
  /**
   * 根据bean的名称获取对象(单例)
   */
  public static Object getBean(String beanName){
    //通过Map容器对应key来获取对应对象
    return beans.get(beanName);  //这里通过Map容器中获取,这样就不会每次都创建一次实例!
  }

//不再使用下面的方法
 /*
  //根据bean的名称获取bean对象
  public static Object getBean(String beanName){
    Object bean=null;
    try {
    String beanPath= props.getProperty(beanName);
    bean = Class.forName(beanPath).newInstance();  //这里的newInstance创建实例(默认无参构造器)每次执行都需要创建一次,这样就不好了
    } catch (Exception e) {
      e.printStackTrace();
    }
    return bean;
  }*/
}

从上面改进的工厂代码,我们可以发现一开始就定义一个Map容器,用于存放创建的对象,为啥要先定义一个Map容器呢?用一个容器将这个实例装起来,这是由于不把这个对象装存起来的话,这个对象不使用很容易被GC掉,何况我们现在只使用这一个对象!

定义一个Map容器存放配置好的文件中的每个对象,之后我们就直接提供一个根据Map的key来取value的getBean方法,这样不仅仅扩展了程序的配置文件的灵活性而且还保证了只产生一个对象,保证资源不浪费,So Beautiful !

那如何证明已经是单例的模式了呢?很简单,如下设计一下service业务层、controller表现层代码即可:

service业务层:添加一个类成员属性,并在方法内部 i++;

public class AccountServiceImpl implements IAccountService {

  private int i=1; //类成员属性

  @Override
  public void save() {
    System.out.println("save成功一个账户....");
    System.out.println(i);
    i++;//二次改革代码
  }
}

controller表现层: 创建调用工厂5次创建对象的方法

/**
 * 这里模拟一个controller调用service
 *
 */
public class AccountCencollert {
  public static void main(String[] args) {
    for(int i=0;i<5;i++){
      IAccountService iaccount= (IAccountService) BeanFactory.getBean("accountService");
      System.out.println(iaccount); //打印的是五个不同的对象,说明是多例的
      iaccount.save(); //会发现打印的i值都是1,并没有自增成功
    }
  }

运行代码结果:

com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
2
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
3
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
4
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
5

发现,确实5个对象都是同一个,并且出现了改变类成员属性的现象。

如果我们把类成员属性改为局部成员属性呢?

public class AccountServiceImpl implements IAccountService {

  @Override
  public void save() {
    int i=1; //局部成员属性
    System.out.println("save成功一个账户....");
    System.out.println(i);
    i++;
  }
}

运行结果

com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
1
com.factory.service.impl.AccountServiceImpl@1540e19d
save成功一个账户....
1

看到这个结果,我们就能联想到,之前为什么servlet中为啥要避免定义类成员,原因就在这里!多例情况下,就不会出现这种情况!!!!

6、结语

到这里我们已经基本了解了程序间的耦合与解耦,并且对单例多例也一并进行了进一步的了解。而spring中正是使用工厂模式来实现程序解耦的,spring是一个大工厂, 或许你并没有察觉哈哈哈哈.....

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 推荐一个ASP的内容管理框架(ASP Nuke)的优势和适用场景
    本文推荐了一个ASP的内容管理框架ASP Nuke,并介绍了其主要功能和特点。ASP Nuke支持文章新闻管理、投票、论坛等主要内容,并可以自定义模块。最新版本为0.8,虽然目前仍处于Alpha状态,但作者表示会继续更新完善。文章还分析了使用ASP的原因,包括ASP相对较小、易于部署和较简单等优势,适用于建立门户、网站的组织和小公司等场景。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了如何在MySQL中将零值替换为先前的非零值的方法,包括使用内联查询和更新查询。同时还提供了选择正确值的方法。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 在数据分析工作中,我们通常会遇到这样的问题,一个业务部门由若干业务组构成,需要筛选出每个业务组里业绩前N名的业务员。这其实是一个分组排序的 ... [详细]
  • 本文介绍了adg架构设置在企业数据治理中的应用。随着信息技术的发展,企业IT系统的快速发展使得数据成为企业业务增长的新动力,但同时也带来了数据冗余、数据难发现、效率低下、资源消耗等问题。本文讨论了企业面临的几类尖锐问题,并提出了解决方案,包括确保库表结构与系统测试版本一致、避免数据冗余、快速定位问题等。此外,本文还探讨了adg架构在大版本升级、上云服务和微服务治理方面的应用。通过本文的介绍,读者可以了解到adg架构设置的重要性及其在企业数据治理中的应用。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
author-avatar
fggdsfgwjff4
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有