Spring 框架学习2 - 程序间的耦合

22 篇文章 0 订阅
11 篇文章 0 订阅

1. 编写 jdbc 的工程代码用于分析程序的耦合

1.1 什么是程序的耦合

​ 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的负载型、调用模块的方式以及通过界面传输数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

​ 在软件工程中,耦合指的就是对象之间的依赖性。对象之间的耦合越高,为何成本越高。因此对象的设计应使类和构建之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标注。划分模块的一个准则就是高内聚低耦合。

1.2 耦合有如下分类:

  1. 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被成为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
  2. 公共耦合。两个或者两个以上的模块共同引用了一个全局数据项,这种耦合被称为全局耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是非常困难的。
  3. 外部耦合。一个模组都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表达传递该全局变量的信息,则称之为外部耦合。
  4. 控制耦合。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合成为控制耦合。
  5. 标记耦合。若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合。
  6. 数据耦合。模块之间通过参数来传递数据,那么被称之为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些具有意义的功能,旺旺需要将某些陌路爱的输出数据作为另一些模块的输入数据。
  7. 非直接耦合。两个某块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

1.3 总结:

​ 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

1.4 内聚与耦合:

​ 内聚标志一个模块内各个元素彼此结合的紧密程度,他是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事情。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。陈谷研究的低耦合,高内聚。就是同一模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却不要那么紧密。

​ 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合的。在进行软件设计时,应尽力做到高内聚,低耦合。

1.5 耦合的例子

​ 在 Java 中,耦合意味着程序间的依赖关系。

​ 它包括:1. 类之间的依赖。 2。 方法间的依赖

​ 以下是 jdbc 连接的代码:

package com.runoob.test;
 
import java.sql.*;
 
public class MySQLDemo {
 
    public static void main(String[] args) throws Exception{
        // 1. 注册驱动
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        // 2. 获取连接
        Connection conn = DriverManager.getConnection("");
        // 3. 获取操作预处理对象
        PreparedStatement pstm = conn.preparedStatement("");
        // 4. 执行 sql 语句,得到结果集
        ResultSet rs = pstm.executeQuery();
        // 5. 遍历结果集
        while(rs.next) {
            System.out.println(rs.getString(""));
        }
        // 6. 释放资源
        rs.close();
        pstm.close();
        conn.closer();
    }
}

​ 类之间的依赖,比如,我们在使用 mysql-connection-java 这个mysql连接工具的时候,我们必须导入 com.mysql.jdbc.Driver 包。如果我们完全放弃这个jar包,我们就无法使用相关的工具,所以,不可能完全放弃类之间的依赖。我们能做的就是降低程序之间的依赖关系。这就是解耦

​ 我们现在这个情况,(比如以上在注册驱动的时候)在编译期就开始具体依赖某个类或者某个jar包(com.mysql.jdbc.Driver)。那么,这个类的独立性会非常差。当我们想将其中的连接数据库的代码抽取出来的时候,会发现,在解除(com.mysql.jdbc.Driver)依赖之前,很难抽取出来。

​ 所以,我们在实际开发中,应该做到编译期不依赖,运行时才依赖

​ 上面的代码,可以使用以下方法,来做到解耦。

​ 我们在创建连接的时候,注册驱动一般使用的以下的方法:

// 1. 注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");

​ 在这里,com.mysql.jdbc.Driver 仅仅是一个字符串,也就是说,不再依赖具体某个驱动类。这个好处是,我们可以对这一段代码进行独立。但是,这个是无法运行的。因为,没有这个驱动类(并没有导入)。当我们运行时,会报出异常(Exception)。这是一个运行时异常,意味着在编译期是没有问题的。

​ 这就完成对mysql连接驱动类的解耦。但是,Class.forName中的驱动变成了一段写死的字符串,以后如果更换了数据,意味着我们需要进入项目内部,全部重写,这又增加了一段耦合。所以,我们需要将配置信息写进配置文件中,通过配置文件读取配置。

​ 所以,解耦的思路如下:

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

2. 对于网页项目的分析和解耦

​ 这里我们选择使用一个普通的 java maven 项目来模拟一个网页项目。

2.1 项目的搭建

  1. 首先是我们的模拟持久层的dao接口:

    package com.selflearning.spring.dao;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    }
    
    
  2. 接下里是持久层的dao接口的实现类:

    package com.selflearning.spring.dao.impl;
    
    import com.selflearning.spring.dao.IAccountDao;
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
        @Override
        public void saveAccount() {
            System.out.println("保存了账户");
        }
    }
    
  3. 然后是我们的模拟业务层的dao接口:

    package com.selflearning.spring.service;
    
    /**
     * 业务层的接口 - 操作账户
     */
    public interface IAccountService {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    
  4. 业务层dao接口的实现类:

    package com.selflearning.spring.service.impl;
    
    import com.selflearning.spring.dao.IAccountDao;
    import com.selflearning.spring.dao.impl.AccountDaoImpl;
    import com.selflearning.spring.service.IAccountService;
    
    /**
     * 模拟账户的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        private IAccountDao accountDao = new AccountDaoImpl();
    
        @Override
        public void saveAccount() {
            accountDao.saveAccount();
        }
    }
    
  5. 最后是模拟显示层(在后面的项目中一般是 servlet ):

    package com.selflearning.spring.ui;
    
    
    import com.selflearning.spring.service.IAccountService;
    import com.selflearning.spring.service.impl.AccountServiceImpl;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
    
        public static void main(String[] args) {
            IAccountService as = new AccountServiceImpl();
            as.saveAccount();
        }
    
    }
    
    
  6. 运行:

    保存了账户
    

但是,这里我们业务层和显示层都选择通过 new 一个对象来调用其中的方法,这无疑增加了类之间的耦合度(这样编写的代码,具有很强的耦合性,独立性非常差)

2.2 通过编写工厂类和独立配置文件来解耦

​ 工厂模式,实现了创建者和调用者之间的解耦。意味着调用者并不需要直接参与对象的创建,对象是通过工厂模式来创建,然后提供给调用者使用。这样,就解决了之前业务层会实例化持久层,显示层会创建业务层对象导致类和类之间耦合度变高的情况。

​ 这里,由于之前编写的模拟业务层和模拟显示层都是可以反复被利用的,所以这里选择编写一个 BeanFactory 来创建可重用的组件。

​ 这里提一下计算机语言中的 Bean:

​ 在计算机语言中,Bean 指可以重用的组件。

​ JavaBean 值用 Java 语言编写的可重用的类。

2.2.1 独立出配置文件

由于是使用工厂类创建业务层和持久层的可重用组件,我们将业务层和持久层的实现类写入配置文件:

accountService=com.selflearning.spring.service.impl.AccountServiceImpl
accountDao=com.selflearning.spring.dao.impl.AccountDaoImpl

2.2.2 使用工厂模式进行解耦

  1. 创建好配置文件之后,创建 factory 文件,在该文件夹下创建工厂类:

    package com.selflearning.spring.factory;
    
    import java.io.InputStream;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * 一个创建 Bean 对象的工厂
     * <p>
     * Bean:在计算机用语中,有可重用组件的意思。
     * JavaBean:用Java语言编写的可重用组件。
     * JavaBean > 实体类(可重用组件的一部分)
     * 它就是创建我们的 service 和 dao 对象的。
     * <p>
     * 第一个:需要一个配置文件来配置我们的service 和dao
     * 配置内容:唯一标志=全限定类名 (key=value)
     * 第二个:通过读取配置文件中配置文件的内容,反射创建对象
     * 配置文件可以是 xml 也可以是 properties
     */
    public class BeanFactory {
        // 定义一个 properties 对象
        private static Properties props;
    
        // 定义一个 Map,用于存放我们要创建的对象。成为容器。
        private static Map<String, Object> beans;
    
        // 使用静态代码为 Properties 对象赋值
        static {
            try {
                // 实例化 Properties 对象
                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
                    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 对象
         * @param beanName
         * @return
    
        public static Object getBean(String beanName) {
            Object bean = null;
            try {
                String beanPath = props.getProperty(beanName);
                bean = Class.forName(beanPath).newInstance();
            } catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return bean;
        }
        **/
    
        /**
         * 这里我们发现,我们每调用一次工厂类,都会创建一个对象。由于并没有在类中
         * 创建静态的属性,所以不会出现多线程共同调用同一个对象而导致数据发生改变的
         * 情况,所以可以将工厂类创建的对象改成单例。
         * 由于创建的单例对象,一段时间不使用,会被 java 的垃圾回收机制回收,所以
         * 当单例对象创建完毕,必须将其存起来。
         */
        public static Object getBean(String name) {
            return beans.get(name);
        }
    
    }
    
  2. 然后,修改业务层和显示层的调用者代码:

    • 业务层

      package com.selflearning.spring.service.impl;
      
      import com.selflearning.spring.dao.IAccountDao;
      import com.selflearning.spring.dao.impl.AccountDaoImpl;
      import com.selflearning.spring.factory.BeanFactory;
      import com.selflearning.spring.service.IAccountService;
      
      /**
       * 账户的业务层实现类
       */
      public class AccountServiceImpl implements IAccountService {
      
      //    private IAccountDao accountDao = new AccountDaoImpl();
          private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
      
          @Override
          public void saveAccount() {
              accountDao.saveAccount();
          }
      }
      
    • 显示层

      package com.selflearning.spring.ui;
      
      
      import com.selflearning.spring.factory.BeanFactory;
      import com.selflearning.spring.service.IAccountService;
      import com.selflearning.spring.service.impl.AccountServiceImpl;
      
      /**
       * 模拟一个表现层,用于调用业务层
       */
      public class Client {
      
          public static void main(String[] args) {
      //        IAccountService as = new AccountServiceImpl();
              IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
              as.saveAccount();
          }
      
      }
      
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值