如何在Java中利用IOC控制反转

发布时间:2021-02-22 15:38:48 作者:Leah
来源:亿速云 阅读:103

这篇文章给大家介绍如何在Java中利用IOC控制反转,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

Java的特点有哪些

Java的特点有哪些 1.Java语言作为静态面向对象编程语言的代表,实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。 2.Java具有简单性、面向对象、分布式、安全性、平台独立与可移植性、动态性等特点。 3.使用Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。

IOC范式揭秘

控制反转是一种带有某些特征的模式。下面,给出了由Martin Fowler给出的一个IOC经典范例,该范例实现的功能是从控制台中收集用户数据。

public static void main(String[] args) {
  while (true) {
    BufferedReader userInputReader = new BufferedReader(
        new InputStreamReader(System.in));
    System.out.println("Please enter some text: ");
    try {
      System.out.println(userInputReader.readLine());
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

这个用例中,在main方法中进行流程控制:在无限循环调用中,读取用户输入,并将读取的内容输出到控制台上。完全又main方法控制何时去读取用户输入,何时去输出。

考虑下,上述程序的一个新版本,该版本中需要通过图形界面中的文本框来收件用户输入,另外还有个按钮,该按钮上绑定有一个action监听器。这样的话,用户每次点击按钮,输入的文本由监听器收集并打印到面板。

在这个版本的程序中,它实际上是由事件监听器模型(在这种情况下,这是框架)的控制下,调用开发者编写的用于读取和打印用户输入的代码。简单地说,框架将调用开发人员的代码,而不是其他方式。该框架实际上是一个可扩展的结构,它为开发人员提供了一组注入自定义代码段的切入点。

这种情况下,控制已经被有效的反转了。

从更通用的角度来看,由框架定义的每个可调用扩展点(以接口实现,实现继承(也称为子类)的形式)是IoC的一种明确定义的形式。

看下,下述这个简单的Servlet例子:

public class MyServlet extends HttpServlet {
 
  protected void doPost(
      HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // developer implementation here
  }
 
  protected void doGet(
      HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // developer implementation here
  }
 
}

此处,HttpServlet类(属于框架)是完全控制程序的元素,而不是MyServlet这个子类。在由servlet容器创建之后,当收到servlet的GET和POST的HTTP请求,doGet()和doPost()方法中的代码会分别自动调用。

与典型的继承方式相比,即子类是控制的元素,而不是基类,该例中,控件已经被反转了。

事实上,servlet的方法是模板方法模式的实现,稍后我们再深入讨论。

使用那些通过提供可扩展API,秉承开闭原则的框架时,使用框架的开发人员的角色,最终被归结为定义自己的一组自定义类,即开发人员要么通过实现框架提供的一个或多个接口方式,要么通过继承现有基类的方式。反过来,类的实例却是直接框架进行实例化,并且这些事例是被框架调用的。

此处引用Fowler的话:该框架调用开发人员,而不是开发人员调用该框架。因此,IoC通常被称为好莱坞原则:不要打电话给我们,我们会打电话给你。

IOC的实现方式

该问题上,显而易见的是,实现控制反转是有几种不同方法的。我们不妨来总结一下,那些常见的实现方式。

注入依赖实现IOC
如前所述,注入依赖是IOC的一种实现方式,而且是最常见的一种面向对象设计方式。但是,思考一下:注入依赖究竟是如何达到控制反转效果的呢?

为了回答这个问题,我们给出如下一个原始的例子:

public interface UserQueue { 
  void add(User user); 
  void remove(User user);
  User get();
 
}
 
public abstract class AbstractUserQueue implements UserQueue {
  protected LinkedList<User> queue = new LinkedList<>();
 
  @Override
  public void add(User user) {
    queue.addFirst(user);
  }
 
  @Override
  public void remove(User user) {
    queue.remove(user);
  }
 
  @Override
  public abstract User get();
 
}
 
public class UserFifoQueue extends AbstractUserQueue { 
  public User get() {
    return queue.getLast();
  }
 
}
 
public class UserLifoQueue extends AbstractUserQueue {
  public User get() {
    return queue.getFirst();
  }
 
}

UserQueue 接口定义了公共的API,用于在一个队列中去存放User对象(为了简单明了,此处忽略User的具体实现)。AbstractUserQueue则是为后续的继承类,提供了一些公用的方法实现。最后的UserFifoQueue 和 UserLifoQueue,则是分别实现了FIFO 和 LIFO 队列。

这是,实现子类多态性的一种有效方式。但是这具体用什么来买我们好处呢?实际上,好处还是蛮多的。

通过创建一个依赖于UserQueue抽象类型(也称为DI术语中的服务)的客户端类,可以在运行时注入不同的实现,无需会重构使用客户端类的代码:

public class UserProcessor { 
  private UserQueue userQueue;
 
  public UserProcessor(UserQueue userQueue) {
    this.userQueue = userQueue;
  }
 
  public void process() {
    // process queued users here
  }
 
}

UserProcessor展示了,注入依赖确实是IOC的一种方式。

我们可以通过一些硬编码方式 如 new 操作,直接在构造函数中实例化在UserProcessor中获取对队列的依赖关系。但是,这是典型的代码硬编程,它引入了客户端类与其依赖关系之间的强耦合,并大大降低了可测性。耳边警钟声声想起啦!不是吗?是的,这样设计真的很挫。

该类在构造函数中声明对抽象类 UserQueue 的依赖。也就是说,依赖关系不再通过 在构造函数中使用 new 操作, 相反,通过外部注入的方式,要么使用依赖注入框架(如CDI和谷歌的Guice),要么使用factory或builders模式。

简而言之,使用DI,客户端类的依赖关系的控制,不再位于这些类中;而是在注入器中进行:

public static void main(String[] args) {
   UserFifoQueue fifoQueue = new UserFifoQueue();
   fifoQueue.add(new User("user1"));
   fifoQueue.add(new User("user2"));
   fifoQueue.add(new User("user3"));
   UserProcessor userProcessor = new UserProcessor(fifoQueue);
   userProcessor.process();
}

上述方式达到了预期效果,而且对UserLifoQueue的注入也简单明了。显而易见,DI确实是实现IOC的一种方式(该例中,DI是实现IOC的一个中间层)。

观察者模式实现IOC

直接通过观察者模式实现IOC,也是一种常见的直观方式。广义上讲,通过观察者实现IOC,与前文提到的通过GUI界面中的action监听器方式类似。但是在使用action监听器情况下,只有在特定的用户事件发生时(点击鼠标,键盘或窗口事件等),才会发生调用。观察者模式通常用于在模型视图的上下文中,跟踪模型对象的状态的变迁。

在一个典型的实现中,一到多个观察者绑定到可观察对象(也称为模式术语中的主题),例如通过调用addObserver方法进行绑定。一旦定义了被观察者和观察者之间的绑定,则被观察者状态的变迁都会触发调用观察者的操作。

为了深入了解这个概念,给出如下例子:

@FunctionalInterface
public interface SubjectObserver {
 
  void update();
 
}

值发生改变时,会触发调用上述这个很简单的观察者。真实情况下,通常会提供功能更丰富的API,如需要保存变化的实例,或者新旧值,但是这些都不需要观察action(行为)模式,所以这里举例尽量简单。

下面,给出一个被观察者类:

public class User {
 
  private String name;
  private List<SubjectObserver> observers = new ArrayList<>();
 
  public User(String name) {
    this.name = name;
  }
 
  public void setName(String name) {
    this.name = name;
    notifyObservers();
  }
 
  public String getName() {
    return name;
  }
 
  public void addObserver(SubjectObserver observer) {
    observers.add(observer);
  }
 
  public void deleteObserver(SubjectObserver observer) {
    observers.remove(observer);
  }
 
  private void notifyObservers(){
    observers.stream().forEach(observer -> observer.update());
  }
}

User类中,当通过setter方法变更其状态事,都会触发调用绑定到它的观察者。

使用主题观察者和主题,以下是实例给出了观察方式:

public static void main(String[] args) {
  User user = new User("John");
  user.addObserver(() -> System.out.println(
      "Observable subject " + user + " has changed its state."));
  user.setName("Jack");
}

每当User对象的状态通过setter方法进行修改时,观察者将被通知并向控制台打印出一条消息。到目前为止,给出了观察者模式的一个简单用例。不过,通过这个看似简单的用例,我们了解到在这种情况下控制是如何实现反转的。

观察者模式下,主题就是起到”框架层“的作用,它完全主导何时何地去触发谁的调用。观察者的主动权被外放,因为观察者无法主导自己何时被调用(只要它们已经被注册到某个主题中的话)。这意味着,实际上我们可以发现控制被反转的”事发地“ – - – 当观察者绑定到主题时:

user.addObserver(() -> System.out.println(
      "Observable subject " + user + " has changed its state."));

上述用例,简要说明了为什么,观察者模式(或GUI驱动环境中的action监听器)是实现IoC的一种非常简单的方式。正是以这种分散式设计软件组件的形式,使得控制得以发生反转。

通过模板方法模式实现IoC

模板方法模式实现的思想是在一个基类中通过几个抽象方法(也称算法步骤)来定义一个通用的算法,然后让子类提供具体的实现,这样保证算法结构不变。

我们可以应用这个思想,定义一个通用的算法来处理领域实体:

public abstract class EntityProcessor {
 
  public final void processEntity() {
    getEntityData();
    createEntity();
    validateEntity();
    persistEntity();
  }
 
  protected abstract void getEntityData();
  protected abstract void createEntity();
  protected abstract void validateEntity();
  protected abstract void persistEntity();
 
}

processEntity() 方法是个模板方法,它定义了处理实体的算法,而抽象方法代表了算法的步骤,它们必须在子类中实现。通过多次继承 EntityProcessor 并实现不同的抽象方法,可以实现若干算法版本。

虽然这说清楚了模板方法模式背后的动机,但人们可能想知道为什么这是 IoC 的模式。

典型的继承中,子类调用基类中定义的方法。而这种模式下,相对真实的情况是:子类实现的方法(算法步骤)被基类的模板方法调用。因此,控制实际是在基类中进行的,而不是在子类中。
这也是 IoC 的典型例子,通过分层结构实现。这种情况下,模板方法只是可调的扩展点的一个漂亮的名字,被开发者用来管理自己的一系列实现。

关于如何在Java中利用IOC控制反转就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

推荐阅读:
  1. java中的ioc是什么
  2. 怎么在Spring IOC中利用注解启动

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java ioc

上一篇:php和js使用正则常用方法有哪些

下一篇:利用java怎么将文件与byte进行转换

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》