控制反转之运用工厂模式进行程序解耦

本文详细解析了程序的耦合问题及其解决方案,介绍了如何通过工厂模式实现解耦,提高代码的可维护性和扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先写一个jdbc的程序来说明编译器依赖问题

package com.itheima.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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();
    }
}

pom添加依赖

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.20</version>
</dependency>

将依赖注释,这段程序会出现错误,并且在编译器不会通过。这就叫做程序的耦合,耦合可以理解为程序间的依赖关系,包括类之间依赖和方法之间的依赖。

解耦就是降低程序间的依赖关系。

实际开发中,应该做到编译器不依赖,运行时才依赖。

解耦思路

  1. 使用反射创建对象,避免使用new关键字
  2. 通过读取配置文件获取要创建的对象全限定类名

分析曾经代码中类似问题

  • AccountDao.java
  • AccountDaoImpl.java
  • AccountService.java
  • AccountServiceImpl.java
  • Client.java

AccountDao.java

package com.itheima.dao;

/**
 * 账户持久层接口
 */
public interface AccountDao {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

AccountDaoImpl.java

package com.itheima.dao.impl;

import com.itheima.dao.AccountDao;

/**
 * 账户持久层实现类
 */
public class AccountDaoImpl implements AccountDao {
    public void saveAccount() {
        System.out.println("保存了账户");
    }
}

AccountService.java

package com.itheima.service;

/**
 * 账户业务层的接口
 */
public interface AccountService {
    // 模拟保存账户
    void saveAccount();
}

AccountServiceImpl.java

package com.itheima.service.impl;

import com.itheima.dao.AccountDao;
import com.itheima.dao.impl.AccountDaoImpl;
import com.itheima.service.AccountService;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDaoImpl();
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

Client.java

package com.itheima.ui;

import com.itheima.service.AccountService;
import com.itheima.service.impl.AccountServiceImpl;

public class Client {
    public static void main(String[] args) {
        AccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}

程序中存在依赖关系:

  • AccountService as = new AccountServiceImpl();
  • private AccountDao accountDao = new AccountDaoImpl();
工厂模式解耦

BeanFactory.java

package com.itheima.factory;

import java.io.InputStream;
import java.util.Properties;

/**
 * 一个创建bean对象的工厂
 *
 * bean:在计算机英语中,有可重用组件的含义
 * javabean:用java语言编写的可重用组件
 *
 * 它就是创建我们的service和dao对象的
 *
 *  思路:
 *      需要一个配置文件来配置我们的service和dao
 *          配置的内容:唯一标识=全限定类名(key:value)
 *      通过读取配置文件中配置的内容,反射创建对象
 *  配置文件可以是xml也可以是properties
 */
public class BeanFactory {

    //定义一个properties对象
    private static Properties props;
    //使用静态代码块为properties对象赋值
    static {
        try {
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
        } catch(Exception e) {
            throw new ExceptionInInitializerError("初始化properties失败");
        }
    }

    /**
     * 根据bean名称获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
            System.out.println(beanPath);
            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

将之前的两段代码进行修改:

//AccountService as = new AccountServiceImpl();
AccountService as = (AccountService)BeanFactory.getBean("accountService");

//private AccountDao accountDao = new AccountDaoImpl();
private AccountDao  accountDao = (AccountDao)BeanFactory.getBean("accountDao");

这样就完成了工厂模式的解耦。

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

多例模式被创建多次,执行效率没有单例对象高。

单例模式只被创建一次,从而类中的成员也就只会初始化一次。

BeanFactory.java

package com.itheima.factory;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 一个创建bean对象的工厂
 *
 * bean:在计算机英语中,有可重用组件的含义
 * javabean:用java语言编写的可重用组件
 *
 * 它就是创建我们的service和dao对象的
 *
 *  思路:
 *      需要一个配置文件来配置我们的service和dao
 *          配置的内容:唯一标识=全限定类名(key:value)
 *      通过读取配置文件中配置的内容,反射创建对象
 *  配置文件可以是xml也可以是properties
 */
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);
    }
    /**
     * 根据bean名称获取bean对象
     * @param beanName
     * @return
     *
    public static Object getBean(String beanName) {
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
//            System.out.println(beanPath);
            bean = Class.forName(beanPath).newInstance();
        }catch(Exception e){
            e.printStackTrace();
        }
        return bean;
    }*/
}

Client.java

package com.itheima.ui;

import com.itheima.dao.AccountDao;
import com.itheima.factory.BeanFactory;
import com.itheima.service.AccountService;
import com.itheima.service.impl.AccountServiceImpl;

public class Client {
    public static void main(String[] args) {
        //AccountService as = new AccountServiceImpl();
        for(int i=0; i<5; i++) {
            AccountService as = (AccountService