美文网首页
02. 程序的耦合

02. 程序的耦合

作者: 吃伏冒有礼貌 | 来源:发表于2019-08-27 23:56 被阅读0次

通过编写JDBC的工程代码来分析程序的耦合

首先创建Maven工程,加入mysql的依赖,编写JDBC程序.


①创建一个maven工程
②创建一个maven工程
③在Pom.xml添加mysql的依赖

④创建一张简单的表

create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

⑤编写JDBC程序

public class JdbcDemo1 {
    public static void main(String[] args) throws SQLException {
        //注册驱动
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        //获取连接
        Connection Connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","3264421");
        //获取操作数据库的预处理对象
        PreparedStatement pstm = Connection.prepareStatement("select * from account");
        //执行sql语句 得到结果集
        ResultSet rs = pstm.executeQuery();
        //遍历,封装结果集
        while(rs.next()){
            System.out.println(rs.getString("name"));
        }
        //释放资源
        rs.close();
        pstm.close();
        Connection.close();
    }

编译期的依赖

如果我们现在在pom.xml中注释掉了mysql的依赖,那么在编译期间,程序就会报错.

   <dependencies>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
       <!-- <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>-->
    </dependencies>
image.png
         //注册驱动
        //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        Class.forName("com.mysql.jdbc.Driver");

↑ 如果我们用以上方式来注册驱动,那么在编译期间就不会报错了.它只是一段字符串,我们不再依赖于某个驱动类.但是不要妄想它能运行,因为类还是不存在的,如果运行,会抛出异常.如果再把注释掉的代码再放开,就能正常运行了.

但以上方式还是会有缺点,这个字符串已经写死了,如果要换数据库,这个驱动还是需要更改的.如果想要解决这个问题,可以通过配置文件

程序的耦合和解耦思路分析

程序的耦合
耦合:程序间的依赖关系
包括:
- 类之间的依赖
- 方法间的依赖
解耦:降低程序类的依赖关系
实际开发中:
应该做到:编译期不依赖,运行时才依赖
解耦思路:

  • 第一步:使用反射来创建对象 而避免使用 new 关键字
  • 通过读取配置文件读取来获取创建对象的全限定名

从① 编写JDBC的工程代码来分析程序的耦合性里我们知道,在实际开发中如果要降低程序的耦合性,一个是 使用反射来创建对象,而避免使用new关键字,第二个是通过读取配置文件读取来获得类的全限定类名.

/**
①账户业务层接口
**/
public interface IAccountService{
  void saveAccount();
}

/**
②账户持久层接口
**/
public interface IAccountDao{
 void saveAccount();
}

/**
③账户持久层
**/
public class AccountDaoImpl implements IAccountDao(){
 public void saveAccount() {
        System.out.println("保存了");
    }
}
/**
④账户业务层
**/
public class AccountServiceImpl implements IAccountService(){
 //模拟保存账户
  IAccountDao dao =  new AccountDaoImpl();
  dao.saveAccount();
}
//模拟一个表现层
public class Client {
    public static void main(String[] args) {
        IAccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}

所以我们可以看到,在这些地方,有很强的耦合性.


image.png
image.png
image.png

如果类出现改动,变会在编译期间就有了异常.

怎样降低这个依赖关系?

使用工厂模式
Bean:在计算机语言中,有可重用组件的含义
JavaBean:用Java语言编写的可重用组件
JavaBean > 实体类
(Service 和 Dao 可以被重复使用)
它就是为我们创建Service 和 dao的

怎么创建工厂模式?

  • 需要一个配置文件来配置 Service 和 Dao
    怎么配置: 唯一标识 = 全限定类名(key = value)
  • 通过读取配置文件中配置的内容,反射创建对象.

创建配置文件 可以是 xml 格式 也可以是properties 格式
properties是比较简单的方式,仅限这个地方.

① 先创建一个bean.properties,放在resource目录下面.(如果是Maven工程的话)


bean.properties
accountService = com.itheima.service.impl
accountDao = com.itheima.dao.impl

②创建一个BeanFactory来读取Bean.properties

public class BeanFactory{
//定义一个properties对象
priavte static Properties props;
//使用静态代码块为properties赋值(?)
  static{
    try{
        //实例化对象 耦合只能被降低而不能被消除 所以这里还是用了new来实例化对象
        props = new Properties();
        //获取Properties流对象
        /**老师说这里不要 写成     InputStream in = new FileInputStream()
        因为不知道这里写什么路径,如果写src的话,WEB工程一旦部署,相对路径src就没有了,如果
        写绝对路径,也不能保证绝对路径(D盘C盘)都存在
        应该写成:
        InputStream in = beanFactory.class.getClassLoader().getResourceAsStream("bean.properties")
        因为创建在Resrouce文件夹下的文件,会成为类根路径下的文件
        **/
        InputStream in = BeanFactory.class.getResourceAsStream("bean.properties");
        }catch(Exception e){
        throw new ExceptionInInitializerError("初始化properties失败");
        }
}
//写一个方法,getBean
//思路:第一个方法类型为Object 因为写死一个具体的返回值,这个方法将不够复用;第二个,getBean的时候,要get哪个bean呢,所以需要一个beanName的参数,根据BeanName的名称,获取Bean对象)
public Object getBean(String BeanName){
      Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
        //在我的环境里,newInstance已经弃用了,JAVA9后不推荐使用
            bean = Class.forName(beanPath).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
}

[关于ResourceAsStream的用法] (https://blog.csdn.net/u012557538/article/details/50317663)

然后怎么解耦呢?
我们现在面临的关系 就是程序间耦合的关系 一个是Service 依赖 Dao,一个是UI依赖Service
所以,在④中,把用new创建对象 改成 用工厂模式的getBean方法得到对象,就能完成解耦.

/**
④账户业务层
**/
public class AccountServiceImpl implements IAccountService {
    /**
     * 模拟保存账户
     */
    //private IAccountDao dao = new AccountDaoImpl();
    private IAccountDao dao = (IAccountDao) BeanFactory.getBean("accountDao");
    public void saveAccount() {
            dao.saveAccount();
    }
}

分析工厂模式里的问题并改造

在以上的例子中,仍存在问题.
我们把Client 改一下,写了个for循环再打印

/**
 * 模拟一个表现层用于调用业务层
 */
public class Client {
    public static void main(String[] args) {
        //IAccountService as = new AccountServiceImpl();
       // as.saveAccount();
        for (int i = 0; i <5 ; i++) {
            IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
        }
    }
}

得到的结果是5个不同的对象,此时的对象是多例的

com.itheima.service.impl.AccountServiceImpl@12edcd21
com.itheima.service.impl.AccountServiceImpl@34c45dca
com.itheima.service.impl.AccountServiceImpl@52cc8049
com.itheima.service.impl.AccountServiceImpl@5b6f7412
com.itheima.service.impl.AccountServiceImpl@27973e9b

那么什么是单例对象呢?
单例对象从始至终都只有一个对象实例,也就是说打印五次,也只有一个实例.(Servlet 就是单例对象)
多例对象和单例对象有什么区别呢?
当我们在Service里 定义一个类成员 private int i = 1,然后打印,同时让 i++
这个时候 i 是一个类成员 在方法里进行++ 也就是说 我们的方法可以操作类成员属性并且改变它的取值

public class AccountServiceImpl implements IAccountService {
    /**
     * 模拟保存账户
     */
    //private IAccountDao dao = new AccountDaoImpl();
    private IAccountDao dao = (IAccountDao) BeanFactory.getBean("accountDao");
    private int i = 1;
    public void saveAccount() {
        dao.saveAccount();
        System.out.println(i);
        i++;
    }
}

这个时候再把 Client 里的as.saveAccount放出来

/**
 * 模拟一个表现层用于调用业务层
 */
public class Client {
    public static void main(String[] args) {
        //IAccountService as = new AccountServiceImpl();
        for (int i = 0; i <5 ; i++) {
            IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
            as.saveAccount();
        }
    }
}

得到的结果是
再执行过程中,因为每个对象由于都有独立实例,从而保证了类对象在创建时重新初始化类中的属性,所以类中i一直都是1,不管有多少个service对象来访问,由于对象都是新创建的,所以 i 的值不会有变化.

com.itheima.service.impl.AccountServiceImpl@12edcd21
保存了
1
com.itheima.service.impl.AccountServiceImpl@34c45dca
保存了
1
com.itheima.service.impl.AccountServiceImpl@52cc8049
保存了
1
com.itheima.service.impl.AccountServiceImpl@5b6f7412
保存了
1
com.itheima.service.impl.AccountServiceImpl@27973e9b
保存了
1

单例对象的特点

多例的
与多例相比,单例对象应该是这样的,这个对象只被创建了一次,从而类中的成员 也只会初始化一次
单例的
所以说 在多个线程访问或者多个对象访问时 由于对象实例只有一个 那这个时候操作类成员的时候 类成员在方法能够进行改变的时候 单例对象有线程问题

多例对象没有这个问题 但多例对象由于被创建多次 执行效率没有单例对象高
在Service 和Dao 的代码中 不存在类属性被方法调用 并且方法能调整的成员变量 所以我们的代码中不存在线程问题的 因为没有在单位对象中可以改变的类成员
(复习:方法每次都能初始化)
所以这样看的话,在这些代码中,由于不存在线程问题,所以不需要多例对象.
但是 由于BeanFactory在创建对象的时候 使用了newInstance,这句话的出场表明了每次都会调用默认构造函数创建对象 所以每次都是新的对象.
经过以上分析 我们并不需要多例模式 我们需要单例模式,因此我们需要对方法进行调整,调整的前提是我们的对象只能newInstance一次

问题

如果创建对象完了不存起来,由于Java的垃圾回收机制,在长时间不用的时候回收,下次再用的时候就没有了.

我们现在只能用一次newInstance ,对象创建出来后应该马上存起来

存到哪儿去呢?我们怎么存呢?

能存这些对象的,我们称之为容器.
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.getResourceAsStream("bean.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) {
        System.out.println("beanName:"+beanName);
        return beans.get(beanName);
    }

这里由多例转为单例运行时报了空指针异常,因为Dao 为null


image.png

在评论上看到有人说


image.png

(未解决)

相关文章

  • 02. 程序的耦合

    通过编写JDBC的工程代码来分析程序的耦合 首先创建Maven工程,加入mysql的依赖,编写JDBC程序. ④创...

  • 【Spring】 03 - Spring IOC

    1. 程序的耦合 1.1 什么是程序的耦合 ? 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。...

  • Spring总结(一)--耦合--IOC

    spring-程序的耦合 什么是程序的耦合? 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦...

  • 程序的耦合

    程序间的依赖关系:类之间的依赖,方法间的依赖 解耦:降低程序间的依赖关系 实际开发中尽量做到编译器不依赖,运行期才...

  • 程序的耦合和解耦思路

    1.什么是程序的耦合? 耦合性(Coupling),也叫耦合度,是对程序间关联程度的度量。耦合的强弱取决于模块间接...

  • Chapter 13 《Packages and Imports

    在大程序中,减少耦合是很重要的,耦合是指程序不同部分依赖其他部分的程度。低耦合能够减少局部修改对其它部分造成的影响...

  • Spring笔记

    1、程序的耦合:耦合:程序间的依赖关系包括:(1)类之间的依赖 (2)方法间的依赖2、解耦:降低程序间的依赖...

  • 委托、通知传值的用法与区别

    低耦合性是良好程序的特性。低耦合性程序可读性和可维护性比较好。Cocoa中的委托、通知功能可以使低耦合性更易实现,...

  • 02 Spring IOC

    什么是程序的耦合性? 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口...

  • 耦合和解耦合

    程序的耦合 耦合:程序间的依赖关系包括:类之间的依赖方法间的依赖 解耦:降低程序间的依赖关系 实际开发中应该做到:...

网友评论

      本文标题:02. 程序的耦合

      本文链接:https://www.haomeiwen.com/subject/zrihsctx.html