编程思想 控制反转(IOC)

控制反转,IOC,Inversion of Control。

一、传统方式

1. 说明

由于对象单一职责原则,如果一个对象要完成某件事情,往往要依赖于其它对象。

在传统的做法中,如果程序中一个对象依赖另外一个对象(比如需要调用该对象的实例方法),就在该对象内部通过 new 的方式直接创建其依赖的对象。

这种做法有两大特点:

  • 对象的依赖关系写死在程序之中,影响了程序的可扩展性
  • 对象之间相互依赖、严重耦合,增大了维护成本

2. 示例

  • XxxDao 接口

    1
    2
    3
    public interface UserDao {
    void getUser();
    }
  • XxxDapImpl 实现类

    1
    2
    3
    4
    5
    6
    public class UserDaoImpl implements UserDao{
    @Override
    public void getUser() {
    System.out.println("获取用户数据");
    }
    }
  • XxxService 接口

    1
    2
    3
    public interface UserService {
    void getUser();
    }
  • XxxServiceImpl 实现类

    1
    2
    3
    4
    5
    6
    7
    8
    public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    @Override
    public void getUser() {
    userDao.getUser();
    }
    }
  • Client 调用方

    1
    2
    3
    4
    5
    6
    public class CustomerClient {
    public static void main(String[] args) {
    UserService userService = new UserServiceImpl();
    userService.getUser();
    }
    }

在 UserServiceImpl 类中需要调用 UserDao 的方法,因此直接实例化其实现类。

3. 缺点

(1) 可扩展性差

假设现在 UserDao 又有了其它实现类 UserDaoImplMySQL、UserDaoImplOracle,希望能够换用不同的实现类。

一个容易想到的解决方式是修改源代码:

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {
// private UserDao userDao = new UserDaoImpl();
// private UserDao userDao = new UserDaoImplMySQL();
private UserDao userDao = new UserDaoImplOracle();

@Override
public void getUser() {
userDao.getUser();
}
}

由于对象的依赖关系写死在程序之中,一旦用户提出新需求便需要修改源代码,可扩展性差。

(2) 维护成本高

假设现在希望将 UserDaoImpl 类更改为 UserDaoImplMySQL 类。

但如果有不止一个对象依赖了 UserDaoImpl 类,便需要进行大量重复修改。

由于对象之间相互依赖、严重耦合,一旦需要修改某个对象,便需要相应修改所有依赖它的对象,增大了维护成本。

二、IOC 方法

1. IOC

为解决传统方式的两个缺点,IOC 应运而生。

IOC,即控制反转。所谓的控制反转,就是把对对象的控制权由程序本身转移到外部,不再让程序创建和管理对象,而是让程序被动地接收对象。

2. 简单示例

  • XxxDao 接口

    1
    2
    3
    public interface UserDao {
    void getUser();
    }
  • XxxDapImpl 实现类

    1
    2
    3
    4
    5
    6
    public class UserDaoImpl implements UserDao{
    @Override
    public void getUser() {
    System.out.println("获取用户数据");
    }
    }
  • XxxDapImplMySQL 实现类2

    1
    2
    3
    4
    5
    6
    public class UserDaoImplMySQL implements UserDao {
    @Override
    public void getUser() {
    System.out.println("MySQL获取用户数据");
    }
    }
  • XxxService 接口

    1
    2
    3
    public interface UserService {
    void getUser();
    }
  • XxxServiceImpl 实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
    }

    @Override
    public void getUser() {
    userDao.getUser();
    }
    }
  • Client 调用方

    1
    2
    3
    4
    5
    6
    7
    public class CustomerClient {
    public static void main(String[] args) {
    UserService userService = new UserServiceImpl();
    ((UserServiceImpl)userService).setUserDao(new UserDaoImplMySQL());
    userService.getUser();
    }
    }

通过 UserServiceImpl 类的 setUserDao() 方法,使得调用方可以主动控制对象的创建,而程序只是定义接口,并被动地等待对象。

三、IOC 的实现

1. 依赖注入(DI)

容器会负责把依赖装配进对象之中。

2. 依赖查找(DL)

当对象需要依赖时,通过容器提供的方法向容器获取。