程序的耦合

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

1.1 SQL文件

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `money` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, 'aaa', '1000');
INSERT INTO `account` VALUES (2, 'bbb', '1000');
INSERT INTO `account` VALUES (3, 'ccc', '1000');

SET FOREIGN_KEY_CHECKS = 1;

1.2 依赖jar包的maven坐标

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.17version>
dependency>
<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <scope>testscope>
dependency>

1.3 应用示例

  • 示例:
package com.sunxiaping;

import org.junit.Test;

import java.sql.*;

public class Spring5Test {

    @Test
    public void test() throws SQLException {
        //注册驱动
        DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        //获取连接
        String url = "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "123456";

        Connection connection = DriverManager.getConnection(url, username, password);

        //获取操作数据库的预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement("select * from account");
        //执行SQL,得到结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //遍历结果集
        while (resultSet.next()) {
            String name = resultSet.getString("name");
            System.out.println("name:" + name);
        }
        //关闭连接
        if (resultSet != null) {
            resultSet.close();
        }
        if (connection != null) {
            connection.close();
        }
    }
}

 

2 程序的耦合

2.1 什么是程序的耦合?

  • 耦合性,也叫耦合度,是对模块间关联程序的度量。耦合的强弱取决于模块间结构和复杂性、调用模块的方式以及通过界面传递数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差(减低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
  • 在软件工程中,耦合指的就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件的耦合最小、软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。

2.2 耦合的分类

2.2.1 内容耦合

  • 当一个模块直接修改或操作另一个模块的数据的时候,或一个模块不通过正常入口而转入另一个模块的时候,这样的耦合被称为内容耦合。
  • 内容耦合是最高程序的耦合,应当避免使用之。

2.2.2 公共耦合

  • 两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是那个模块给全局变量赋了一个特定的值是十分困难的。

2.2.3 外部耦合

  • 一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数传递该全局变量的信息,则称之为外部耦合。

2.2.4 控制耦合

  • 一个模块通过接口向另一个模块传递一个控制信息,接受信息的模块根据信号值而进行适当的动作,这种模块被称为控制耦合。

2.2.5 标记耦合

  • 如果一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和模块C之间存在一个标记耦合。

2.2.6 数据耦合

  • 模块之间通过参数来传递数据,那么称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般存在这种类型的偶尔还,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。

2.2.7 非直接耦合

  • 两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调度来实现的。

2.3 总结

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

2.4 内聚和耦合

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

2.5 实际开发过程中的依赖关系优化

  • 我们在开发中,有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。

 

  • 比如:
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.dao.impl.AccountDaoImpl;
import com.sunxiaping.spring5.service.IAccountService;

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();

}
  • 上面的代码表示:业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层的实现类,编译将不能通过。这种编译期依赖关系,应该在我们开发中杜绝,我们需要优化代码来解决。

 

  • 比如:
package com.sunxiaping;

import org.junit.Test;

import java.sql.*;

public class Spring5Test {

    @Test
    public void test() throws SQLException {
        //注册驱动
        DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        //获取连接
        String url = "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "123456";

        Connection connection = DriverManager.getConnection(url, username, password);

        //获取操作数据库的预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement("select * from account");
        //执行SQL,得到结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //遍历结果集
        while (resultSet.next()) {
            String name = resultSet.getString("name");
            System.out.println("name:" + name);
        }
        //关闭连接
        if (resultSet != null) {
            resultSet.close();
        }
        if (connection != null) {
            connection.close();
        }
    }
}
View Code
  • 早期我们的JDBC操作,注册驱动的时候,我们为什么不使用DriverManager的register方法,而是采用Class.forName的方式?就是因为我们的类依赖了数据库的具体驱动类,如果这时候更换了数据库产品,需要修改源代码来重新加载数据库驱动。这显然不是我们所想要的。

 

3 解决程序耦合的思路

  • 当我们使用JDBC的时候,是通过反射来注册驱动的。比如:
Class.forName("com.mysql.cj.jdbc.Driver");
  • 此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除MySQL的驱动jar包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。
  • 同时,也产生了一个新的问题,MySQL驱动的全限定名字符串是在Java类中写死的,一旦要改,还是要修改源码。
  • 解决这个问题也很简单,使用配置文件配置。

 

  • 在实际开发过程中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并保存起来。在接下来使用的时候,直接拿过来用就好了。
  • 那么,这个读取配置文件,创建和获取三层对象的类就是工厂。

 

4 使用工厂模式解耦

4.1 bean.properties

accountDao=com.sunxiaping.spring5.dao.impl.AccountDaoImpl
accountService=com.sunxiaping.spring5.service.impl.AccountServiceImpl

4.2 dao和service层的代码

  • IAccountDao.java
package com.sunxiaping.spring5.dao;

public interface IAccountDao {

    void saveAccount();
}
  • AccountDaoImpl.java
package com.sunxiaping.spring5.dao.impl;

import com.sunxiaping.spring5.dao.IAccountDao;

public class AccountDaoImpl implements IAccountDao {

    @Override
    public void saveAccount() {
        System.out.println("保存账户信息");
    }
}
  • IAccountSevice.java
package com.sunxiaping.spring5.service;

public interface IAccountService {

    void saveAccount();
}
  • AccountServiceImpl.java
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.factory.BeanFactory;
import com.sunxiaping.spring5.service.IAccountService;

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

4.3 工厂类BeanFactory

  • 示例:
package com.sunxiaping.spring5.factory;

import java.io.IOException;
import java.util.Properties;

/**
 * 工厂类
 */
public class BeanFactory {
    private static Properties properties;

    static {
        try {
            properties = new Properties();
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"));
        } catch (IOException e) {
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 根据bean的名称获取Bean的对象
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        Object obj = null;
        try {
            String beanAllName = properties.getProperty(beanName);
            obj = Class.forName(beanAllName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return obj;
    }


}

 

你可能感兴趣的:(程序的耦合)