Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung, wie Java drei Entwurfsmuster der IOC-Kontrollumkehr verwendet

Detaillierte Erläuterung, wie Java drei Entwurfsmuster der IOC-Kontrollumkehr verwendet

黄舟
黄舟Original
2017-10-18 10:16:561531Durchsuche

Dieser Artikel stellt hauptsächlich die drei Entwurfsmuster von Java vor, die die IOC-Kontrollinversion verwenden. Er hat einen bestimmten Referenzwert.

Für viele Entwickler ist die Inversion der Kontrolle (IoC) wichtig ein vages Konzept, da sie in der realen Welt kaum oder gar keine Anwendung finden. Im besten Fall kann man sich Inversion of Control (IoC) als Äquivalent zur Dependency Injection (DI) vorstellen. Tatsächlich gelten die Umkehrung der Kontrolle und die Abhängigkeitsinjektion nur dann als gleichwertig, wenn beide Seiten nur die Umkehrung der Abhängigkeitsverwaltungskontrolle widerspiegeln. Obwohl die Abhängigkeitsinjektion tatsächlich eine bekannte Form von IoC ist. Tatsächlich ist IoC jedoch ein relativ umfassenderes Software-Design-Paradigma, das durch eine Vielzahl von Mustern implementiert werden kann. In diesem Artikel stellen wir vor, wie Dependency Injection, Observer Pattern und Template Method Pattern die Inversion of Control implementieren.

Wie viele andere Entwurfsmuster, die aus verschiedenen Nutzungsszenarien zusammengefasst werden, ist auch die Implementierung von IoC eine ähnliche Kompromissmethode, die für Entwickler geeignet ist:

Einerseits ist das Design von hoch Entkoppelte Komponenten und die Kapselung der Anwendungslogik an einem einzigen Ort sind eine direkte und natürliche Möglichkeit, IoC zu implementieren.
Andererseits erfordert die obige Implementierung den Aufbau mindestens einer Indirektionsschicht, was in einigen Anwendungsfällen ein Überdesign sein kann.
Als nächstes schauen wir uns einige spezifische Implementierungen an, die Ihnen helfen werden, zu verstehen, wie Sie Kompromisse zwischen diesen Eigenschaften eingehen können.

IOC-Paradigma enthüllt

Kontrollumkehr ist ein Muster mit bestimmten Merkmalen. Nachfolgend finden Sie ein klassisches IOC-Beispiel von Martin Fowler, das die Funktion zum Sammeln von Benutzerdaten von der Konsole implementiert.


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

In diesem Anwendungsfall wird die Flusskontrolle in der Hauptmethode durchgeführt: Im Endlosschleifenaufruf werden Benutzereingaben gelesen und die gelesene Inhaltsausgabe gespeichert zur Konsole. Die Hauptmethode steuert vollständig, wann Benutzereingaben gelesen und wann ausgegeben werden.

Stellen Sie sich eine neue Version des oben genannten Programms vor. In dieser Version müssen Benutzereingaben über ein Textfeld in der grafischen Oberfläche empfangen werden. Es gibt auch eine Schaltfläche mit einem daran gebundenen Aktions-Listener. In diesem Fall wird jedes Mal, wenn der Benutzer auf die Schaltfläche klickt, der eingegebene Text vom Hörer erfasst und auf dem Panel gedruckt.

In dieser Version des Programms wird es tatsächlich vom Entwickler aufgerufen, um Benutzereingaben zu lesen und zu drucken, unter der Kontrolle des Codes des Ereignis-Listener-Modells (in diesem Fall ist dies das Framework). Einfach ausgedrückt: Das Framework ruft den Code des Entwicklers auf und nicht umgekehrt. Das Framework ist eigentlich eine erweiterbare Struktur, die Entwicklern eine Reihe von Einstiegspunkten zum Einfügen benutzerdefinierter Codefragmente bietet.

In diesem Fall wurde die Kontrolle effektiv umgekehrt.

Aus einer allgemeineren Perspektive ist jeder aufrufbare Erweiterungspunkt, der durch ein Framework (in Form einer Schnittstellenimplementierung, Implementierungsvererbung (auch Unterklasse genannt)) definiert wird, eine genau definierte Form von IoC.

Sehen Sie sich das folgende einfache Servlet-Beispiel an:


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

Hier hat die HttpServlet-Klasse (die zum Framework gehört) die volle Kontrolle Elemente des Programms, nicht die Unterklasse MyServlet. Nach der Erstellung durch den Servlet-Container wird der Code in den Methoden doGet() und doPost() automatisch aufgerufen, wenn die GET- und POST-HTTP-Anforderungen des Servlets empfangen werden.

Im Gegensatz zur typischen Vererbung, bei der die Unterklasse das Element des Steuerelements und nicht die Basisklasse ist, wurde das Steuerelement in diesem Beispiel invertiert.

Tatsächlich ist die Servlet-Methode die Implementierung des Vorlagenmethodenmusters, auf das wir später ausführlicher eingehen werden.

Bei der Verwendung von Frameworks, die dem Open-Closed-Prinzip folgen, indem sie erweiterbare APIs bereitstellen, läuft die Rolle des Entwicklers, der das Framework verwendet, letztendlich darauf hinaus, seinen eigenen Satz benutzerdefinierter Klassen zu definieren, d. h. der Entwickler implementiert entweder das Framework One oder mehr Schnittstellen bereitgestellt, entweder durch Erben von einer vorhandenen Basisklasse. Instanzen von Klassen werden wiederum direkt vom Framework instanziiert und diese Instanzen werden vom Framework aufgerufen.

Um hier Fowler zu zitieren: Das Framework ruft den Entwickler auf, und nicht der Entwickler ruft das Framework auf. Deshalb wird IoC oft als Hollywood-Prinzip bezeichnet: Rufen Sie uns nicht an, wir rufen Sie an.

So implementieren Sie IOC

Zu diesem Thema ist es offensichtlich, dass es mehrere verschiedene Möglichkeiten gibt, die Umkehrung der Kontrolle zu implementieren. Wir könnten diese gängigen Implementierungsmethoden genauso gut zusammenfassen.

Abhängigkeiten einfügen, um IOC zu implementieren
Wie oben erwähnt, ist das Einfügen von Abhängigkeiten eine Möglichkeit, IOC zu implementieren, und es ist die gebräuchlichste objektorientierte Entwurfsmethode. Denken Sie jedoch darüber nach: Wie wird durch das Einfügen von Abhängigkeiten die Umkehrung des Kontrolleffekts erreicht?

Um diese Frage zu beantworten, geben wir das folgende Originalbeispiel:


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

Die UserQueue-Schnittstelle definiert eine öffentliche API, die To Store verwendet Benutzerobjekte in einer Warteschlange (der Einfachheit und Klarheit halber wird die spezifische Implementierung von Benutzer hier ignoriert). AbstractUserQueue stellt einige öffentliche Methodenimplementierungen für nachfolgende geerbte Klassen bereit. Die letzten UserFifoQueue und UserLifoQueue implementieren FIFO- bzw. LIFO-Warteschlangen.

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

通过创建一个依赖于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 的模式。

Bei der typischen Vererbung rufen Unterklassen Methoden auf, die in der Basisklasse definiert sind. In diesem Modus ist die relativ reale Situation: Die von der Unterklasse implementierte Methode (Algorithmusschritt) wird von der Vorlagenmethode der Basisklasse aufgerufen. Daher befindet sich die Steuerung tatsächlich in der Basisklasse und nicht in der Unterklasse.
Dies ist auch ein typisches Beispiel für IoC, das durch eine Schichtstruktur implementiert wird. In diesem Fall ist die Vorlagenmethode nur ein schicker Name für einen anpassbaren Erweiterungspunkt, den Entwickler zum Verwalten ihrer eigenen Implementierungen verwenden.

Zusammenfassung

Obwohl die Umkehrung der Kontrolle im Java-Ökosystem allgegenwärtig ist, insbesondere in vielen Frameworks, die häufig Abhängigkeitsinjektion verwenden, ist dies für die meisten Entwickler jedoch der Fall noch vage und seine Anwendung wird durch die Abhängigkeitsinjektion eingeschränkt. In diesem Artikel veranschauliche ich dieses Konzept, indem ich mehrere praktische Möglichkeiten zur Implementierung von IoC zeige.

Abhängigkeitsinjektion: In diesen Klassen gibt es keine Kontrolle mehr über den Erhalt von Abhängigkeiten vom Client. Es wird vom zugrunde liegenden Injektor-/DI-Framework verwaltet.
Beobachtermuster: Wenn sich das Subjekt ändert, wird die Kontrolle vom Beobachter auf das Subjekt übertragen.
Template-Methodenmuster: Die Steuerung erfolgt in der Basisklasse, die die Template-Methode definiert, und nicht in der Unterklasse, die die Algorithmusschritte implementiert.
Wie immer wird durch die Analyse jedes Anwendungsfalls bestimmt, wie und wann IoC eingesetzt werden soll. Machen Sie IoC nicht um des IoC willen.

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung, wie Java drei Entwurfsmuster der IOC-Kontrollumkehr verwendet. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn