简单分析程序中耦合和解耦合

為{幸葍}努か 提交于 2020-02-07 11:34:23

什么是程序的耦合

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

简单来说就是,减少模块之间的依赖性,提高程序的独立性

通常,我们在一个程序中,调用另外一个程序的方法,我们一般都会想到用new这个关键词,在学习Spring之前,我们在ServiceImpl中调用DAO中的方法,我们一般会这样写

private IAccountDAO dao = new IAccountDAOImpl();

这样的代码写多了之后会大大的增加程序的耦合度,没有遵循开发中的高内聚,低耦合的开发思想,极大的增加了代码的维护难度,非常不利于代码的维护和调式,所以我们想到在Java发射中我们学过通过newInstance() 对象来获得实例,所以我们可以,通过反射来降低代码之间的耦合度。
但是,要想通过newInstance() 来获取对象的实例就必须得到该类的全限定类名,所以,我们考虑用XML或者是Properties,但是由于properties较为简单,所以此处我们选用properties。
代码如下

//IAccountDAO.java

public interface IAccountDAO {
    void saveAccount();
}
//IAccountDAOImpl.java

public class IAccountDAOImpl implements IAccountDAO {
    @Override
    public void saveAccount() {
        System.out.println("账户保存成功");
    }
}
//IAccountService.java

public interface IAccountService {

    void saveAccount();
}
//IAccountServiceImpl.java

public class IAccountServiceImpl implements IAccountService {
    //private IAccountDAO dao = new IAccountDAOImpl();
    IAccountDAO dao = (IAccountDAO) BeanFactory.getBean("IAccountDAO");
    @Override
    public void saveAccount() {
        dao.saveAccount();
        System.out.println(i);
    }
}
//BeanFactory.java

public class BeanFactory {
    //定义一个Properties对象
    private static Properties properties;
    static {
        try {
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties = new Properties();
            properties.load(in);
        } catch (IOException e) {
            throw new ExceptionInInitializerError("初始化properties异常");
        }
    }

    public static Object getBean(String beanName) {
        Object bean = null;
        try {
        	//通过beanName获取该类的全限定类名
            String property = properties.getProperty(beanName);
            //实例化properties
            bean = Class.forName(property).newInstance();//每次都会调用默认构造函数创建对象
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return bean;
    }
}
//Client.java

public class Client {
    public static void main(String[] args) {
        //IAccountService service = new IAccountServiceImpl();
        IAccountService service = (IAccountService) BeanFactory.getBean("IAccountService");
        service.saveAccount();
    }
}
#bean.properties文件

#需要一个唯一标识来配置我们的Service和Dao,配置内容:唯一标识=全限定类名(key=value)
IAccountService=com.toulan.service.impl.IAccountServiceImpl
IAccountDAO=com.toulan.dao.impl.IAccountDAOImpl

运行结果
在这里插入图片描述
我们发现我们并没有像以前写的那样通篇全是通过new 来实例化对象,减少了代码之间的耦合度。

现在我们将代码进行少许的修改,然后再次运行,修改的代码如下

//IAccountServiceImpl.java

public class IAccountServiceImpl implements IAccountService {
    //private IAccountDAO dao = new IAccountDAOImpl();
    IAccountDAO dao = (IAccountDAO) BeanFactory.getBean("IAccountDAO");
    public int i = 1;
    @Override
    public void saveAccount() {
        dao.saveAccount();
        System.out.println(i);
        i++;
    }
}
//Client.java

public class Client {
    public static void main(String[] args) {
        //IAccountService service = new IAccountServiceImpl();
        IAccountService service = null;
        for (int i=0;i<5;i++) {
            service = (IAccountService) BeanFactory.getBean("IAccountService");
            System.out.println(service);
            service.saveAccount();
        }
    }
}

运行结果
在这里插入图片描述
通过运行结果我们发现,虽然我们在for循环里面什么也没做,但是结果中我们发现打印出来的对象都不一样,但是我们期望它是一样的,不一样的原因是因为每次调用newInstance() 这个方法它都会执行该类的无参构造器,从而来创建一个新的对象。其实我们不太需要每次都来创建一个对象,而每次创建一个新的对象,对内存的消耗也是不小的,所以我们可以考虑使用单例模式,但是newInstance() 每次调用都会创建一个新的对象,所以如果我们的同一对象使用多次,我们可以考虑只newInstance 一次,但是如果我们创建一个对象而长时间不去使用它,那么就会因为Java的垃圾回收机制导致该对象被回收,导致我们下次使用的时候对象已经被回收而导致无法使用,所以,我们要将实例化的对象装入一个新的容器之中去,那么,我们就可以定义一个Map<K,V>,用于存放相对应的键值,所以代码修改如下:

//BeanFactory.java

public class BeanFactory {

    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
    private static Map<String,Object> beans;

    //使用静态代码块为Properties对象赋值
    static {
        try {
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            //实例化容器
            beans = new HashMap<String,Object>();
            //取出配置文件中所有的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的名称获取对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }
}

运行结果
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!