在说到这里的时候,首先要说下程序的耦合和解耦,以便对上节做一个解释。
一、程序的耦合和解耦
1.程序的耦合性(Copling)
(1)程序的耦合性,也叫做耦合度,是对模块之间关联程度的度量,耦合性的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合性是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系,模块间的联系越多,程序的耦合性越强,独立性越差,也即降低耦合性可以提高其独立性。
(2)在软件工程中,耦合性就是对象之间的依赖性,对象的耦合越高,维护的成本越高。因此对象的设计应该使得类和构建之间的耦合性最小,软件工程中通常使用耦合度和内聚度来作为衡量模块的独立程度的标准,划分的一个标准是高内聚低耦合。
(3)内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.接下来将用案例的方式来演示程序的耦合。
(1)code
package com.itheima.jdbc; import java.sql.*; /** * 程序的耦合 */ public class JdbcDemo1 { public static void main(String[] args) throws Exception { //1.注册驱动 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //2.获取连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","root"); //3.获取操作数据库的预处理对象 PreparedStatement pstm = conn.prepareStatement("select * from account"); //4.执行sql语句,得到结果集 ResultSet rs=pstm.executeQuery(); //5.遍历结果集 while (rs.next()){ System.out.println(rs.getString("name")); } //6.释放资源 rs.close(); pstm.close(); conn.close(); } }
(2)如上代码中,程序要想运行,没有new com.mysql.jdbc.Driver()这个jar包是不能够运行的,没有jar包会直接在编译期的时候就报错,并不是运行期的异常,在实际开发过程中,有很多这种在编译期就直接报错的代码,
就是说明这个类运行的时候,没有这个jar包的时候,是不能够正常编译的,这个特性就是我们说的程序的耦合性,对于耦合,我们一般认为是程序之间的依赖关系,即类之间的依赖,方法之间的依赖,耦合性只能降低,但是不能够完全的消除依赖关系。在实际开发中,我们应该做到,编译期不依赖,运行时才依赖。
(3)我们可以通过下面的这种方式来降低耦合性。
package com.itheima.jdbc; import java.sql.*; /** * 程序的耦合 */ public class JdbcDemo1 { public static void main(String[] args) throws Exception { //1.注册驱动 // DriverManager.registerDriver(new com.mysql.jdbc.Driver()); Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","root"); //3.获取操作数据库的预处理对象 PreparedStatement pstm = conn.prepareStatement("select * from account"); //4.执行sql语句,得到结果集 ResultSet rs=pstm.executeQuery(); //5.遍历结果集 while (rs.next()){ System.out.println(rs.getString("name")); } //6.释放资源 rs.close(); pstm.close(); conn.close(); } }
(4)使用类加载的形式来加载一个字符串,我们不再依赖于某个具体的驱动类。这样带来的好处就是可以使得这个类比较独立,
能通过编译期,就算要报错,最后也是在运行时报错。所以我们解决类依赖的基本思路是:在创建对象的时候,使用反射来创建对象,而避免使用new关键字来创建对象。但是上面这个类将这个字符串在类中写死了,如果将来想在类中换一个数据库,这个地方的代码还是需要进行修改。这个时候我们可以通过读取配置文件来获取要创建的对象的全限定类名的方式,使用配置文件将其配置进去,然后使用配置文件的方式来将其读取出来得到要读取对象的全限定类名,然后使用反射来创建对象。
(5)如下所示三层调用模型中,存在很强的耦合性,使得代码的独立性很差。
/** * Dao类账户的持久层接口 */ public interface AccountDao { void saveAccount(); } import com.itheima.dao.AccountDao; /** * 账户的持久层实现类 */ public class AccountDaoImpl implements AccountDao { public void saveAccount() { System.out.println("AccountDao say hello world"); } }
/** * Service类业务层的接口,操作账户 */ public interface AccountService { void saveAccount(); } import com.itheima.dao.AccountDao; import com.itheima.dao.impl.AccountDaoImpl; import com.itheima.service.AccountService; /** * * 账户的业务层实现类 */ public class AccountServiceImpl implements AccountService { //下面通过new来创建对象的方式是我们应该避免的地方 private AccountDao accountDao = new AccountDaoImpl(); public void saveAccount() { accountDao.saveAccount(); } }
import com.itheima.service.AccountService; import com.itheima.service.com.itheima.service.impl.AccountServiceImpl; /** * 模拟一个表现层,用于调用业务层 */ public class Client { public static void main(String[] args){ AccountService as = new AccountServiceImpl(); as.saveAccount(); } }
上面这种通过New的方式来调用类,当某个类出错的时候,会在编译期报错,会使得类的独立性很差,耦合性很强。
(6)接下来我们通过工厂模式解耦
Dao层 package com.itheima.dao; /** * 账户的持久层接口 */ public interface AccountDao { void saveAccount(); } //DaoService层 package com.itheima.dao.impl; import com.itheima.dao.AccountDao; /** * 账户的持久层实现类 */ public class AccountDaoImpl implements AccountDao { public void saveAccount() { System.out.println("AccountDao say hello world"); } }
Service层
package com.itheima.service; /** * 业务层的接口,操作账户 */ public interface AccountService { void saveAccount(); } package com.itheima.service.impl; import com.itheima.dao.AccountDao; import com.itheima.dao.impl.AccountDaoImpl; import com.itheima.factory.BeanFactory; import com.itheima.service.AccountService; /** * * 账户的业务层实现类 */ public class AccountServiceImpl implements AccountService { //下面通过new来创建对象的方式是我们应该避免的地方 // private AccountDao accountDao = new AccountDaoImpl(); private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao"); public void saveAccount() { accountDao.saveAccount(); } }
//Client
package com.itheima.ui; import com.itheima.factory.BeanFactory; import com.itheima.service.AccountService; /** * 模拟一个表现层,用于调用业务层 */ public class Client { public static void main(String[] args){ // AccountService as = new AccountServiceImpl(); AccountService as = (AccountService) BeanFactory.getBean("accountService"); as.saveAccount(); } }
//工厂
package com.itheima.factory; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 创建Bean对象的工厂 * Bean在计算机英语中有可重用组件的含义 * JavaBean不等于实体类,JavaBean的范围要远大于实体类。是用Java语言编写的可重用组件 * 它就是创建Service和Dao对象的 * 第一个:需要一个配置文件来配置我们的 ServiceDao * 配置文件的内容:全限定类名=唯一标志对应关系 * 第二个:通过读取配置文件中配置的内容,反射创建Bean对象 * 配置文件可以是xml,也可以是properties */ public class BeanFactory { //定义一个Peoperties对象 private static Properties props; //使用静态代码块为Properties对象赋值 static { try { //实例化对象 props= new Properties(); //获取Properties文件的流对象 InputStream in =BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); } catch (IOException e) { throw new ExceptionInInitializerError("初始化properties失败"); } } /** * 根据bean的名称获取bean对象 * @return */ public static Object getBean(String beanName){ Object bean =null; try { String beanPath = props.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); }catch (Exception e){ e.printStackTrace(); } return bean; } }
//Bean.properties
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl