Methods for implementing the Observer pattern using JAVA8 (top) _java

Source: Internet
Author: User
Tags anonymous static class visibility

The Observer (Observer) mode, also known as publish-subscribe (publish/subscribe) mode, is a four-person group (GoF, that is, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) is presented in the 1994 co-author of the design pattern: The basics of reusable object-oriented software (see 293-313 pages in the book). Although this pattern has a long history, it is still widely used in a variety of scenarios and even becomes an integral part of the standard Java library. While there are already a lot of articles on the observer model, they are focused on the implementation in Java, ignoring the various problems developers encounter when using the Observer pattern in Java.

The purpose of this paper is to fill this void: This article mainly introduces the implementation of the observer pattern by using the JAVA8 architecture, and further discusses the complex problems of classical schemas, including anonymous inner classes, lambda expressions, thread safety, and non-trivial time-consuming observer implementations. Although the content of this article is not comprehensive, many of the complex problems involved in this model, is far from an article can be said clearly. But after reading this article, the reader can understand what the observer pattern is, how versatile it is in Java, and how to deal with some of the common problems in implementing the Observer pattern in Java.

Observer mode

According to the classical definition proposed by GoF, the main thrust of the Observer pattern is:

Defines a one-to-many dependency between objects, and when the state of an object changes, all objects that depend on it are notified and automatically updated.

What does that mean? In many software applications, the state of objects is interdependent. For example, if an application focuses on numerical data processing, the data may be displayed in graphical user interface (GUI) tables or graphs or both, i.e., when the underlying data is updated, the corresponding GUI component is also updated. The crux of the problem is how to update the GUI components when the underlying data is updated, while minimizing the coupling between the GUI component and the underlying data.

A simple and extensible solution is a reference to the table and the image GUI component of the object that manages the underlying data, allowing the object to notify the GUI component when the underlying data changes. Obviously, this simple solution will quickly show up for complex applications that handle more GUI components. For example, there are 20 GUI components that depend on the underlying data, and the objects that manage the underlying data need to maintain references to these 20 components. As the number of objects dependent on related data increases, the degree of coupling between data management and objects becomes difficult to control.

Another better solution is to allow object registrations to get permission for data updates that are of interest, and the data manager notifies those objects when the data changes. In layman's parlance, let the data object of interest tell the manager: "Notify me when data changes." In addition, these objects can not only register for update notifications, but also unregister to ensure that the data manager no longer notifies the object when the data changes. In the original definition of GoF, the object registering for the update is called "Observer" (Observer), the corresponding data manager is called "goal" (Subject), the data that the observer is interested in is called "Target State", the registration process is called "add" (attach), and the process of withdrawing the observation is called "remove" (detach). As already mentioned, the Observer mode is also called the Publish-subscribe model, which can be understood as a customer subscribing to the target observer, and when the target status is updated, the target publishes the updates to the Subscriber (this design pattern extends to the common architecture, called the Publish-subscribe architecture). These concepts can be expressed in the following class diagram:


A specific observer (CONCERETEOBSERVER) is used to receive updated state changes while passing a reference to a specific topic (conceretesubject) to its constructor. This provides specific observers with references to specific topics that can be updated as the state changes. In simple terms, the specific observer is told to update the topic, using references in its constructor to get the state of the specific topic, and finally storing the retrieval state objects under the specific observer's observation State (Observerstate) attribute. This process is shown in the following sequence diagram:


Specialization of the classic model

Although the observer pattern is generic, there are a number of specialized patterns, the most common being the following two kinds:

Provides a parameter to the state object and passes to the Update method called by the Observer. In Classic mode, when the observer is notified that the subject state changes, it obtains its updated state directly from the subject. This requires the observer to hold an object reference that points to the get state. This creates a circular reference, concretesubject the reference to its observer list, and the Concreteobserver reference points to the concretesubject that can get the subject state. In addition to the status of the update, there is no connection between the observer and the subject of the registered listener, and the observer is concerned with the state object rather than the subject itself. That is, in many cases, concreteobserver and ConcreteSubject are forcibly associated, and instead, when ConcreteSubject calls the update function, the state object is passed to Concreteobserver, There is no need to correlate. The association between Concreteobserver and state objects reduces the degree of dependency between the observer and the State (see Martin Fowler's article for more differences between association and dependencies).

Merges the subject abstract class and the ConcreteSubject into a Singlesubject class. In most cases, subject use of abstract classes does not enhance the flexibility and scalability of the program, so merging this abstract class with a specific class simplifies the design.

After these two specialized patterns are combined, their simplified class diagram is as follows:


In these specialized models, the static class structure is greatly simplified, and the interaction between classes is simplified. The sequence diagram at this point is as follows:


Another feature of the specialization pattern is the deletion of the Concreteobserver member variable observerstate. Sometimes a specific observer does not need to save the latest state of the Subject, but only to monitor the state of the Subject status update. For example, if the observer updates the value of the member variable to the standard output, the observerstate can be deleted, thus removing the association between the Concreteobserver and the State class.

More common naming conventions

Classic mode and even the aforementioned specialization mode are used in terms of Attach,detach and observer, and many of the Java implementations are in different dictionaries, including Register,unregister,listener. It is worth mentioning that State is the listener of all objects that need to be monitored for change, and the specific name of the status object needs to be seen in the viewer pattern. For example, in the Observer mode under the Listener Listener event scenario, the registered listener will be notified when the event occurs, at which point the state object is an event, that is, whether it occurred.

The name of the target in the actual application rarely contains subject. For example, create an application about zoos, register multiple listeners to observe the zoo class, and receive notification when new animals enter the zoo. The goal in this case is the zoo class, which, in order to keep the terminology consistent with the given problem field, will not use a term such as subject, which means that the zoo class is not named Zoosubject.

The name of the listener is usually followed by the listener suffix, for example, the monitor that the new animal was added to in the previous article is named Animaladdedlistener. Similarly, functions such as register, unregister, and notify often have their corresponding listener name suffixes, such as Animaladdedlistener's register, unregister, The Notify function is named Registeranimaladdedlistener, Unregisteranimaladdedlistener, and Notifyanimaladdedlisteners, Note that s of the Notify function name, because the Notify function handles multiple rather than single listeners.

The naming is lengthy, and usually a subject registers multiple types of listeners, such as the one mentioned in the zoo, where zoo, in addition to registering for the new listener, will need to register to monitor animals to reduce the listener, There are two kinds of register functions: (Registeranimaladdedlistener and Registeranimalremovedlistener, which handle the type of the listener as a qualifier that represents the type of person it should be observed.) Another solution is to create a registerlistener function and then overload it, but it is easier to know which listener is listening, and overloading is a relatively small practice.

Another idiomatic syntax is to use the on prefix instead of update, such as the update function named onanimaladded instead of updateanimaladded. This situation is more common when listeners are notified of a sequence, such as adding an animal to the list, but is rarely used to update a single piece of data, such as an animal's name.

This article will then use Java's symbolic rules, although the symbolic rules will not change the real design and implementation of the system, but using terminology familiar to other developers is an important development guideline, so familiarize yourself with the Observer pattern notation rules described above in Java. The above concepts are described below in a JAVA8 environment with a simple example.

A simple example

Or the zoo example mentioned earlier, using JAVA8 API interface to implement a simple system that illustrates the rationale of the Observer pattern. The problem is described as:

Create a system zoo that allows users to monitor and revoke the state of adding new objects animal, and create a specific listener that will be responsible for outputting the name of the new animal.

Based on previous learning of observer patterns, it is necessary to create 4 classes, specifically:

Zoo class: The theme in the pattern, which is responsible for storing all the animals in the zoo and notifying all registered listeners when the new animal joins.

Animal class: Represents animal objects.

Animaladdedlistener class: The Observer interface.

Printnameanimaladdedlistener: A specific observer class that is responsible for outputting the name of the new animal.

First we create a animal class, which is a simple Java object that contains the name member variables, constructors, getter, and setter methods, and the code is as follows:

public class Animal { 
private String name;
Public Animal (String name) {
this.name = name;
}
Public String GetName () {return
this.name;
}
public void SetName (String name) {
this.name = name;
}
}

Using this class to represent animal objects, you can then create the Animaladdedlistener interface:

Public interface Animaladdedlistener {public 
void onanimaladded (Animal Animal);
}

The previous two classes are simple, no longer detailed, and then create the Zoo class:

 public class Zoo {private list<animal> animals = new arraylist<> (); privat
E list<animaladdedlistener> listeners = new arraylist<> (); public void Addanimal (Animal Animal) {//ADD the Animal to the list of Animals this.animals.add (Animal);//Notify The L
IST of registered listeners this.notifyanimaladdedlisteners (animal); public void Registeranimaladdedlistener (Animaladdedlistener listener) {//ADD the listener to the list of registered L
Isteners This.listeners.add (listener); } public void Unregisteranimaladdedlistener (Animaladdedlistener listener) {//Remove the listener from the list of the R
Egistered listeners This.listeners.remove (listener); } protected void Notifyanimaladdedlisteners (Animal Animal) {//Notify each of the listeners in the list of registered Li
Steners This.listeners.forEach (Listener-> listener.updateanimaladded (animal)); }
}

The analogy of the previous two is complex, it contains two lists, one to store all the animals in the zoo, the other to store all the listeners, given that the animals and listener collections are simple to store, this article selected the ArrayList to store. The specific data structure of the storage listener depends on the problem, for example, for the zoo problem here, if the listener has priority, then you should choose a different dataset, or rewrite the listener's register algorithm.

The registration and removal implementations are simple delegates: Each listener is added or removed as a parameter from the listener's listener list. The implementation of the Notify function deviates slightly from the standard format of the Observer pattern, which includes input parameters: the newly added animal so that the Notify function can pass the newly added animal reference to the listener. Use the Streams API's foreach function to traverse the listener and perform theonanimaladded functions on each listener.

In the Addanimal function, the new animal object and listener are added to the corresponding list. If you do not consider the complexity of the notification process, this logic should be included in a convenient method of invocation, just passing in a reference to the new animal object, which is why the logical implementation of the notification listener is encapsulated in the Notifyanimaladdedlisteners function. This is also mentioned in the implementation of Addanimal.

In addition to the logic problem of the Notify function, we need to emphasize the controversy over the visibility of the Notify function. In the classic observer model, as GOF on page No. 301 of the design pattern book, the Notify function is public, but although it is used in Classic mode, this does not mean that it must be public. Select visibility should be based on applications, such as the example of the zoo in this article, the Notify function is a protected type and does not require that each object can initiate a notification of a registered observer, just to ensure that the object inherits the function from the parent class. Of course, not exactly, you need to figure out which classes can activate the Notify function, and then determine the visibility of the function.

Next you need to implement the Printnameanimaladdedlistener class, which uses the System.out.println method to add the name output of the new animal, as follows:

public class Printnameanimaladdedlistener implements Animaladdedlistener { 
@Override public
Void updateanimaladded (Animal Animal) {
//Print The name of the newly added Animal
-System.out.println ("added a new" iMAL with Name ' "+ animal.getname () +" ' ");
}

Finally, the main function to implement the driver application:

public class Main {public 
static void Main (string[] args) {
//Create the zoo to store animals
Zoo Zoo = NE W Zoo ();
Register a listener to being notified when the animal is added
zoo.registeranimaladdedlistener (new Printnameanimaladde Dlistener ());
Add an animal notify the registered listeners
Zoo.addanimal (new Animal ("Tiger");
}

The main function simply creates a zoo object, registers a listener with the output animal name, and creates a new animal object to trigger the registered listener, and the final output is:

Added a new animal with name ' Tiger '

New Listener

When the listener is subject and added to the user, the advantage of the observer pattern is fully demonstrated. For example, to add a listener that calculates the total number of animals in zoos, simply create a new listener class and register to the zoo class without having to make any changes to the zoo class. Add Count Listener Countinganimaladdedlistener code as follows:

public class Countinganimaladdedlistener implements Animaladdedlistener { 
private static int animalsaddedcount = 0;< c2/> @Override public
void updateanimaladded (Animal Animal) {
//Increment The number of animals
animalsaddedcount++;
Print the number of animals
System.out.println ("Total animals added:" + animalsaddedcount);
}

The modified main function is as follows:

public class Main {public 
static void Main (string[] args) {
//Create the zoo to store animals
Zoo Zoo = new Zoo ();
Register listeners to was notified when a animal is added
zoo.registeranimaladdedlistener (new printnameanimaladded Listener ());
Zoo.registeranimaladdedlistener (New Countinganimaladdedlistener ());
Add an animal notify the registered listeners
Zoo.addanimal (new Animal ("Tiger");
Zoo.addanimal (New Animal ("Lion"));
Zoo.addanimal (New Animal ("Bear"));
}

The output results are:

Added a new animal with name ' Tiger ' total 
animals added:1 
Added A new animal with name ' Lion ' Total 
anima LS Added:2 
added a new animal with name ' Bear ' 

The consumer can create any listener only if the listener registration code is modified. This extensibility is primarily due to subject and observer interface associations, not directly to Concreteobserver. As long as the interface is not modified, the subject of the calling interface need not be modified.

Anonymous inner class, lambda function and listener registration

A major improvement in JAVA8 is the addition of functional features, such as the addition of lambda functions. Before introducing a lambda function, Java provides similar functionality through anonymous inner classes that are still in use in many existing applications. In observer mode, you can create a new listener at any time without creating a specific observer class, for example, the Printnameanimaladdedlistener class can be implemented in the main function with an anonymous inner class that implements the following code:

public class Main {public 
static void Main (string[] args) {
//Create the zoo to store animals
Zoo Zoo = new Zoo ();
Register listeners to being notified when a animal is added
zoo.registeranimaladdedlistener (New Animaladdedlistener ( {
@Override public
void updateanimaladded (Animal Animal) {
//Print The name of the newly added
Animal System.out.println ("Added a new animal with name" + animal.getname () + "'");
}
);
Add an animal notify the registered listeners
Zoo.addanimal (new Animal ("Tiger");
}

Similarly, lambda functions can be used to accomplish such tasks:

public class Main {public 
static void Main (string[] args) {
//Create the zoo to store animals
Zoo Zoo = new Zoo ();
Register listeners to was notified when a animal is added
zoo.registeranimaladdedlistener (
(animal)-> syste M.out.println ("Added a new animal with name" + animal.getname () + "'")
);
Add an animal notify the registered listeners
Zoo.addanimal (new Animal ("Tiger");
}

It should be noted that the lambda function applies only to a single function of the Listener interface, which, although it looks strict, is actually a simple function of many listeners, as in the example of Animaladdedlistener. If an interface has more than one function, you can choose to use an anonymous inner class.

Implicit registration creates a listener with this problem: Since the object was created within the scope of the registration call, it is not possible to store a reference to a specific listener. This means that listeners registered through a lambda function or anonymous inner class cannot unregister, because the undo function needs to pass in a reference to an already registered listener. An easy way to solve this problem is to return a reference to the registered listener in the Registeranimaladdedlistener function. This allows you to unregister listeners created with a lambda function or anonymous inner class, and the improved method code is as follows:

Public Animaladdedlistener Registeranimaladdedlistener (Animaladdedlistener listener) { 
//ADD The listener to the LI St of registered listeners
This.listeners.add (listener); 
return listener;
}

The client code for the redesigned function interaction is as follows:

public class Main {public 
static void Main (string[] args) {
//Create the zoo to store animals
Zoo Zoo = new Zoo ();
Register listeners to being notified when a animal is added
animaladdedlistener listener = Zoo.registeranimaladdedli Stener (
(animal)-> System.out.println ("Added a new animal with name" + animal.getname () + "'")
);
Add an animal notify the registered listeners
Zoo.addanimal (new Animal ("Tiger");
Unregister the Listener
Zoo.unregisteranimaladdedlistener (listener);
ADD another animal, which'll not print the name, since the listener has been
/previously unregistered
. Addanimal (New Animal ("Lion"));
}

The result output at this time is only added a new animal with name ' Tiger ' because the listener has been dropped before the second animal join:

Added a new animal with name ' Tiger '

If a more complex solution is adopted, the Register function can also return the receipt class for unregister listeners to invoke, for example:

public class Animaladdedlistenerreceipt { 
private final Animaladdedlistener listener;
Public Animaladdedlistenerreceipt (Animaladdedlistener listener) {
This.listener = listener;
}
Public final Animaladdedlistener Getlistener () {return
This.listener
}
}

Receipt will be the return value of the registration function, as well as the Undo registration function input parameter, at this point the zoo implementation looks like this:

public class Zoousingreceipt { 
//... Existing attributes and constructor ...
Public Animaladdedlistenerreceipt Registeranimaladdedlistener (Animaladdedlistener listener) {
//ADD the Listener To the list of registered listeners
This.listeners.add (listener);
return new Animaladdedlistenerreceipt (listener);
}
public void Unregisteranimaladdedlistener (Animaladdedlistenerreceipt receipt) {
//Remove the listener from the list Of the registered listeners
This.listeners.remove (Receipt.getlistener ());
}
// ... Existing notification method ...
}

The receive implementation mechanism described above allows you to save information for the listener to revoke. This means that if the revocation registration algorithm relies on the state of the subject registration listener, this state will be saved, and if the revocation registration only needs to point to a reference to the previously registered listener, then the receiving technology will appear to be troublesome and deprecated.

In addition to the particularly complex, specific listeners, the most common way to register listeners is through a lambda function or through anonymous inner class registration. The exception, of course, is that the class that contains the subject implements the Observer interface and registers a listener that contains the reference target invoked. The case as shown in the following code:

public class Zoocontainer implements Animaladdedlistener { 
private Zoo Zoo = new Zoo ();
Public Zoocontainer () {
//Register This object as a listener
this.zoo.registerAnimalAddedListener (this);
} Public
Zoo Getzoo () {return
this.zoo;
}
@Override public
void updateanimaladded (Animal Animal) {
System.out.println ("Added Animal with name" + Anima L.getname () + "'");
public static void Main (string[] args) {
//Create the Zoo container
zoocontainer zoocontainer = new Zoocontain ER ();
Add an animal notify the innerally notified Listener Zoocontainer.getzoo (
). Addanimal (New Animal ("Tiger");
}
}

This approach works well for simple situations and the code does not look professional, but it is popular with modern Java developers, so it is necessary to understand how this is working. Because Zoocontainer implements the Animaladdedlistener interface, Zoocontainer instances (or objects) can be registered as Animaladdedlistener. Zoocontainer class, the reference represents an instance of the current object, Zoocontainer, so it can be used as a animaladdedlistener.

Typically, it is not required that all container classes implement such functionality, and the container class that implements the listener interface can only invoke the subject registration function, simply passing the reference as the listener's object to the register function. In the following sections, you will describe common problems and solutions for multithreaded environments.

ONEAPM provides you with End-to-end Java application Solutions, and we support all common Java frameworks and application servers to help you quickly identify system bottlenecks and locate the root cause of the anomalies. Minute-level deployment, instant experience, and Java monitoring has never been simpler. To read more technical articles, visit ONEAPM's official technical blog.

The above content for you to introduce the use of JAVA8 to implement the Observer Mode (on) the relevant content, the next article to introduce the use of JAVA8 to implement the Observer mode (next), interested friends continue to learn, I hope to help you!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.