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(); } } }
- 早期我们的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; } }