Maison  >  Article  >  Java  >  Explication détaillée de la façon dont Java utilise trois modèles de conception pour l'inversion de contrôle IOC

Explication détaillée de la façon dont Java utilise trois modèles de conception pour l'inversion de contrôle IOC

黄舟
黄舟original
2017-10-18 10:16:561531parcourir

Cet article présente principalement en détail les trois modèles de conception de Java utilisant l'inversion de contrôle IOC. Il a une certaine valeur de référence. Les amis intéressés peuvent s'y référer

Pour de nombreux développeurs, disons, l'inversion de contrôle (IoC) est. un concept vague car ils ont peu ou pas d’applications dans le monde réel. Dans le meilleur des cas, l’Inversion de Contrôle (IoC) peut être considérée comme l’équivalent de l’Inversion de Dépendances (DI). En fait, le contrôle inversé et l’injection de dépendances ne sont considérés comme équivalents que si les deux côtés ne reflètent que le contrôle de gestion des dépendances inversées. Bien que l’injection de dépendances soit en fait une forme bien connue d’IoC. Cependant, en réalité, l’IoC est un paradigme de conception logicielle relativement plus large qui peut être mis en œuvre à travers une variété de modèles. Dans cet article, nous présenterons comment l'injection de dépendances, le modèle d'observateur et le modèle de méthode de modèle implémentent l'inversion de contrôle.

Tout comme de nombreux autres modèles de conception, résumés à partir de divers scénarios d'utilisation, la mise en œuvre de l'IoC est également une méthode de compromis similaire adaptée aux développeurs :

D'une part, la conception de hautement les composants découplés et l'encapsulation de la logique d'application en un seul endroit sont un moyen direct et naturel de mettre en œuvre l'IoC.
D'un autre côté, l'implémentation ci-dessus nécessite de créer au moins une couche d'indirection, ce qui peut constituer une conception excessive dans certains cas d'utilisation.
Ensuite, examinons quelques implémentations spécifiques, qui vous aideront à comprendre comment faire des compromis entre ces propriétés.

Le paradigme du CIO révélé

L'inversion du contrôle est un modèle présentant certaines caractéristiques. Ci-dessous, un exemple classique d'IOC donné par Martin Fowler est donné, qui implémente la fonction de collecte de données utilisateur à partir de la console.


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

Dans ce cas d'utilisation, le contrôle de flux est effectué dans la méthode principale : dans l'appel en boucle infinie, lisez l'entrée de l'utilisateur et stockez le contenu lu Sortie à la console. La méthode principale contrôle complètement quand lire les entrées de l'utilisateur et quand les sortir.

Considérez une nouvelle version du programme ci-dessus. Dans cette version, la saisie de l'utilisateur doit être reçue via une zone de texte dans l'interface graphique. Il y a également un bouton avec un écouteur d'action qui y est lié. Dans ce cas, chaque fois que l'utilisateur clique sur le bouton, le texte saisi est collecté par l'auditeur et imprimé sur le panneau.

Dans cette version du programme, il est en fait appelé par le développeur pour lire et imprimer les entrées de l'utilisateur, sous le contrôle du code du modèle d'écoute d'événement (dans ce cas, il s'agit du framework). En termes simples, c'est le framework qui appellera le code du développeur et non l'inverse. Le framework est en fait une structure extensible qui fournit aux développeurs un ensemble de points d'entrée pour injecter des extraits de code personnalisés.

Dans ce cas, le contrôle a été effectivement inversé.

D'un point de vue plus général, chaque point d'extension appelable défini par un framework (sous la forme d'une implémentation d'interface, héritage d'implémentation (également appelé sous-classe)) est une forme bien définie d'IoC .

Regardez l'exemple simple de Servlet suivant :


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
  }
 
}

Ici, la classe HttpServlet (appartenant au framework) est en contrôle total Éléments du programme, pas la sous-classe MyServlet. Après avoir été créé par le conteneur de servlet, lorsque les requêtes HTTP GET et POST du servlet sont reçues, le code des méthodes doGet() et doPost() sera automatiquement appelé respectivement.

Contrairement à l'héritage typique, où la sous-classe est l'élément du contrôle plutôt que la classe de base, dans cet exemple, le contrôle a été inversé.

En fait, la méthode servlet est l'implémentation du modèle de méthode modèle, dont nous discuterons en profondeur plus tard.

Lors de l'utilisation de frameworks qui adhèrent au principe ouvert-fermé en fournissant des API extensibles, le rôle du développeur utilisant le framework se résume finalement à définir son propre ensemble de classes personnalisées, c'est-à-dire que le développeur implémente soit le framework One ou plusieurs interfaces fournies, soit en héritant d'une classe de base existante. À leur tour, les instances de classes sont instanciées directement par le framework, et ces instances sont appelées par le framework.

Pour citer Fowler ici : le framework appelle le développeur, plutôt que le développeur qui appelle le framework. C'est pourquoi IoC est souvent appelé le principe hollywoodien : ne nous appelez pas, nous vous appellerons.

Comment mettre en œuvre l'IOC

Sur cette question, il est évident qu'il existe plusieurs manières différentes de mettre en œuvre l'inversion de contrôle. Nous pourrions aussi bien résumer ces méthodes de mise en œuvre courantes.

Injecter des dépendances pour implémenter IOC
Comme mentionné ci-dessus, l'injection de dépendances est un moyen d'implémenter IOC, et c'est la méthode de conception orientée objet la plus courante. Cependant, réfléchissez-y : comment l’injection de dépendances permet-elle d’obtenir l’effet d’inversion du contrôle ?

Pour répondre à cette question, nous donnons l'exemple original suivant :


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

L'interface UserQueue définit une API publique, en utilisant Pour stocker Objets utilisateur dans une file d'attente (pour des raisons de simplicité et de clarté, l'implémentation spécifique de User est ignorée ici). AbstractUserQueue fournit certaines implémentations de méthodes publiques pour les classes héritées ultérieures. Les derniers UserFifoQueue et UserLifoQueue implémentent respectivement les files d'attente FIFO et 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 的模式。

Dans l'héritage typique, les sous-classes appellent des méthodes définies dans la classe de base. Dans ce mode, la situation relativement réelle est la suivante : la méthode (étape d'algorithme) implémentée par la sous-classe est appelée par la méthode modèle de la classe de base. Par conséquent, le contrôle se trouve en réalité dans la classe de base et non dans la sous-classe.
C'est également un exemple typique d'IoC, mis en œuvre via une structure en couches. Dans ce cas, la méthode modèle n'est qu'un nom sophistiqué pour un point d'extension réglable que les développeurs utilisent pour gérer leur propre ensemble d'implémentations.

Résumé

Bien que l'inversion du contrôle soit omniprésente dans l'écosystème Java, en particulier dans de nombreux frameworks qui utilisent couramment l'injection de dépendances, pour la plupart des développeurs, ce modèle est cependant encore vague et son application est limitée par l’injection de dépendances. Dans cet article, j'illustre ce concept en montrant plusieurs façons pratiques de mettre en œuvre l'IoC.

Injection de dépendances : Le contrôle d'obtention des dépendances du client n'existe plus dans ces classes. Il est géré par le framework injecteur/DI sous-jacent.
Modèle d'observateur : lorsque le sujet change, le contrôle est transmis de l'observateur au sujet.
Modèle de méthode modèle : le contrôle s'effectue dans la classe de base qui définit la méthode modèle, plutôt que dans la sous-classe qui implémente les étapes de l'algorithme.
Comme toujours, comment et quand utiliser l'IoC est déterminé en analysant chaque cas d'utilisation, ne faites pas l'IoC pour le plaisir de l'IoC.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn