Java的耦合问题

一、耦合问题

1、软件设计的耦合

耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间的接口的复杂程度、调用模块的方式以及哪些信息通过接口。

  • 软件工程中对象之间的耦合度就是指对象之间的依赖性。
  • 对象之间的耦合越高,维护成本也就越高

2、常见的耦合

  1. 内容耦合
    内容耦合是指一个模块与另一个模块的内部属性有关,不经调用直接使用另一个模块的程序代码或内部数据。
  2. 公共耦合
    公共耦合指通过一个公共数据环境相互作用的那些模块间的耦合,公共数据环境可以是全程变量或数据结构共享的通信,内存的公共覆盖区及任何存储介质上的文件。
    物理设备(也有将公共外部设备分类为外部耦合),由于两个模块都需引用同一个公共数据域,因此有较高的耦合度。
  3. 控制耦合/外部耦合
    一个模块通过接口向另一个模块传递一个控制信号,接收信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
  4. 标记耦合
    若一个模块A通过接口向两个模块B和C传递一个公共参数,那么模块B和模块C之间存在一个标记耦合。
  5. 数据耦合
    模块之间通过参数来传递数据,那么被称为数据耦合。
    数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,为了完成一些有意义的操作,往往需要将某些模块的输出数据作为另一些模块的输入数据
  6. 非直接耦合
    两个模块之间没有直接关系,他们之间的联系完全是通过主模块的控制和调用来实现的。

3、耦合的解决方案

  • 耦合在一起的模块,如果一方发生了变化肯定会影响另一方,这样的话独立性降低危险度提高,维护成本自然就高了。
  • 如果必须用耦合的话,尽量使用数据耦合,少用控制耦合,限制公共耦合范围,避免内容耦合。
  • 高内聚低耦合是软件设计的最终解决思路,但高内聚意味着编码成本的增加,所以完美的高内聚是不存在的,所以适度设计即可。(内聚是指什么功能都由自己内部实现,不需要依靠外部任何资源)

二、耦合的小例子

1、最初版本

  • 实体类
package com.gaj.entity;

/**
 * Java实体类
 * @author Jan
 *
 */
public class UserInfo {
	private Integer id;
	private String name;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString() {
		return "UserInfo [id=" + id + ", name=" + name + "]";
	}
}

  • DAO层
package com.gaj.dao;

import com.gaj.entity.UserInfo;

/**
 * 模拟获得对象及属性
 * @author Jan
 *
 */
public class UserInfoDAO {
	public UserInfo findUserById(Integer id){
		// 创建方法返回值
		UserInfo user = new UserInfo();
		// 设置属性
		user.setId(1);
		user.setName("张三");
		//返回
		return user;
	}
}

  • Service层
package com.gaj.service;

import com.gaj.dao.UserInfoDAO;
import com.gaj.entity.UserInfo;

public class UserInfoService {
	
	private UserInfoDAO userDao = new UserInfoDAO();
	
	public UserInfo findUserById(Integer id){
		return userDao.findUserById(id);
	}
	
}

  • Test类
package com.gaj.test;

import org.junit.Test;

import com.gaj.entity.UserInfo;
import com.gaj.service.UserInfoService;

public class MyTest {

	@Test
	public void findUserById(){
		UserInfoService service = new UserInfoService();
		UserInfo user = service.findUserById(1);
		System.out.println(user);
	}
	
}

测试类是可以拿到结果的,但是类与类之间耦和度比较高。测试类依赖Service层的对象,Service层依赖DAO层的对象,DAO层依赖实体层的对象。
在这里插入图片描述
更改被引用类的类名,会直接影响到引用该类的类
在这里插入图片描述

2、引入接口降低耦合

  • DAO层接口
package com.gaj.dao;

import com.gaj.entity.UserInfo;


public interface UserInfoDAO {
	
	public UserInfo findUserById(Integer id);
	
}

  • DAO层实现类
package com.gaj.dao.Impl;

import com.gaj.dao.UserInfoDAO;
import com.gaj.entity.UserInfo;
/**
 * 模拟获得对象及属性
 * @author Jan
 *
 */
public class UserInfoDAOImplement implements UserInfoDAO{

	@Override
	public UserInfo findUserById(Integer id){
		// 创建方法返回值
		UserInfo user = new UserInfo();
		// 设置属性
		user.setId(1);
		user.setName("张三");
		//返回
		return user;
	}
	
}

  • Service层接口
package com.gaj.service;

import com.gaj.entity.UserInfo;

public interface UserInfoService {

	public UserInfo findUserById(Integer id);
	
}

  • Service层实现类
package com.gaj.service.impl;

import com.gaj.dao.UserInfoDAO;
import com.gaj.dao.Impl.UserInfoDAOImplement;
import com.gaj.entity.UserInfo;
import com.gaj.service.UserInfoService;

public class UserInfoServiceImplement implements UserInfoService {
	
	private UserInfoDAO userDao = new UserInfoDAOImplement();
	
	public UserInfo findUserById(Integer id){
		return userDao.findUserById(id);
	}
	
}

  • 测试类
package com.gaj.test;

import org.junit.Test;

import com.gaj.entity.UserInfo;
import com.gaj.service.UserInfoService;
import com.gaj.service.impl.UserInfoServiceImplement;

public class MyTest {

	@Test
	public void findUserById(){
		UserInfoService service = new UserInfoServiceImplement();
		UserInfo user = service.findUserById(1);
		System.out.println(user);
	}
}

耦合当然是存在的,但如果不new对象,就能减少耦合了。(将控制耦合转化为数据耦合)

3、利用反射技术进一步降低耦合

利用反射技术,将控制耦合转化为数据耦合。

	@Test
	public void findUserById2() throws Exception{
		UserInfoService service = (UserInfoService) Class.forName("com.gaj.service.impl.UserInfoServiceImplement").newInstance();
		UserInfo user = service.findUserById(1);
		System.out.println(user);
	}

把本来的类与类之间的耦合降低为类与字符串之间的耦合,但要创建对象的类的权限定名写在了java文件里,也意味着如果修改这个创建对象的类名的话必须把当前这个java文件重新编译。

4、将XML作为配置文件保存类全限定名

因为XML是程序的元语言文件,XML是不需要任何编译器就能直接被计算机识别,HTML就是XML的一种高级应用,写HTML的时候只
需要一个记事本,写完了无需编译直接扔到浏览器就能运行,所以可以利用XML的特性把要创建对象的类以某种自定义格式扔到XML中,然后用Java技术读取XML,读取到之后再反射生成对象。
语法:

头部标记 : 
<?xml version="1.0" encoding="UTF-8"?>
单标签 : 
<标签名称 属性1="值1" 属性2="值2" .... 属性N="值N" />
双标签 : 
<标签名称 属性1="值1" 属性2="值2" .... 属性N="值N">标签之间的内容</标签名称>

将需要创建对象的部分都放到XML文件中
applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="userDao" class="com.gaj.dao.Impl.UserInfoDAOImplement" />
	<bean id="service" class="com.gaj.service.impl.UserInfoServiceImplement" />
</beans>

5、dom4j工具解析XML文件

dom4j是一个Java的XML API,是jdom的升级品,用来读写XML文件的。dom4j是一个十分优秀的JavaXML API,具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom技术,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面还可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,因此可以知道dom4j无论在哪个方面都是非常出色的。如今可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这已经是必须使用的jar包,Hibernate也用它来读写配置文件。

  • 解析XML的dom4j的jar包:
    在这里插入图片描述
  • 测试dom4j
	@Test
	public void dom4jTest() throws DocumentException{
		// 获取xml的位置
		InputStream in = this.getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
		// 开始解析
		// 1.将xml加载到内存中	相当于在内存中将字节流转化为XML文件
		Document document = new SAXReader().read(in);
		// 2.获取xml的beans根节点
		Element beans = document.getRootElement();
		// 3.获取根节点下的所有bean子节点 保存到List集合
		List<Element> elements = beans.elements("bean");
		// 4.解析id属性和class属性
		for(Element e : elements){
			// 获取id属性
			String id = e.attributeValue("id");
			// 获取class属性
			String clazz = e.attributeValue("class");
			// 输出
			System.out.println(id + "\t\t" + clazz);
		}
	}
}

6、简单工厂模式

拿类名创建对象,然后拿id值作为key,将这些创建的对象再保存到一个Map集合里去,程序用到的时候用map的key获取某个对象这样我们就不用new这个对象了,这个工具类可以通过读取XML的形式创建其他类的对象,也就是说创建所有类的对象都可以通过这一个类来创建,那么这个专门用来创建对象的类就是对象工厂类,而这种设计模式被叫做简单工厂设计模式。
简单工厂模式获取目标对象有什么好处呢?使用工厂模式隐藏创建对象细节,这样可以统一对象管理,如果要修改某些实例对象只需要修改工厂方法即可,这样引用工厂的那些个类都不用修改。

  • 编写工厂类
    BeanFactory.java
package com.gaj.utils;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 工厂类 读取xml文件通过bean标签的class属性值创建对象
 * 
 * @author Jan
 *
 */
public class BeanFactory {

	// 定义对象保存容器
	private static Map<String, Object> objs = new HashMap<String, Object>();

	static {
		// 获取xml的位置
		InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.xml");
		// 1.将xml加载到内存中:字节流 -> xml文件
		try {
			Document document = new SAXReader().read(is);
			// 2.获取xml的根节点 beans
			Element beans = document.getRootElement();
			// 3.获取根节点下的所有子节点 bean
			List<Element> bean = beans.elements("bean");
			// 4. 循环解析id属性和class属性
			for (Element e : bean) {
				String id = e.attributeValue("id");
				String clazz = e.attributeValue("class");
				// 通过类名创建对象
				Object obj = Class.forName(clazz).newInstance();
				// 保存到Map集合
				objs.put(id, obj);
			}
		} catch (DocumentException e1) {
			e1.printStackTrace();
		} catch (InstantiationException e1) {
			e1.printStackTrace();
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		}

	}

	/**
	 * 根据对象id获取对象
	 * 
	 * @param id
	 * @return 返回该id的class类
	 */
	public static <T> T getBean(String id) {
		Object obj = objs.get(id);
		if (null != obj) {
			return (T) obj;
		} else {
			return null;
		}
	}
}

  • 修改Service实现类
package com.gaj.service.impl;

import com.gaj.dao.UserInfoDAO;
import com.gaj.dao.Impl.UserInfoDAOImplement;
import com.gaj.entity.UserInfo;
import com.gaj.service.UserInfoService;
import com.gaj.utils.BeanFactory;

public class UserInfoServiceImplement implements UserInfoService {
	
	private UserInfoDAO userDao = BeanFactory.getBean("userDao");
	
	public UserInfo findUserById(Integer id){
		return userDao.findUserById(id);
	}
	
}

  • 修改测试类
	@Test
	public void findUserById(){
		UserInfoService service = BeanFactory.getBean("service");
		UserInfo user = service.findUserById(1);
		System.out.println(user);
	}

7、自定义IOC

IOC:(Inversion Of Control)控制反转,对象的创建权限由本类交给了工厂创建,对这种对象创建权限的移交叫控制反转。

  • IOC是什么?
    控制反转,把类创建自己对象的权利交给框架,让框架统一管理对象;
  • 为什么要用IOC创建对象呢?
    解耦和,IOC能把两个类的硬耦合(编程阶段耦合在了一起)编程两个类的软耦合(程序运行期间耦合在了一起),这样互不干扰,特别开心;
  • 在哪些地方使用IOC?
    框架应用中使用比较频繁
  • 怎么用IOC?
    1)定义对象容器
    2)工厂模式获取对象
  • IOC的作用?
    消除计算机程序对象的耦合
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值