Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4536

Behavioral Patterns in Core Java

$
0
0

1. Introduction

Recently we looked at Creational Design Patterns and where to find them within the JVM and other core libraries. Now we're going to look at Behavioral Design Patterns. These focus on how our objects interact with each other or how we interact with them.

2. Chain of Responsibility

The Chain of Responsibility pattern allows for objects to implement a common interface and for each implementation to delegate on to the next one if appropriate. This then allows us to build a chain of implementations, where each one performs some actions before or after the call to the next element in the chain:

interface ChainOfResponsibility {
    void perform();
}
class LoggingChain {
    private ChainOfResponsibility delegate;
    public void perform() {
        System.out.println("Starting chain");
        delegate.perform();
        System.out.println("Ending chain");
    }
}

Here we can see an example where our implementation prints out before and after the delegate call.

We aren't required to call on to the delegate. We could decide that we shouldn't do so and instead terminate the chain early. For example, if there were some input parameters, we could have validated them and terminated early if they were invalid.

2.1. Examples in the JVM

Servlet Filters are an example from the JEE ecosystem that works in this way. A single instance receives the servlet request and response, and a FilterChain instance represents the entire chain of filters. Each one should then perform its work and then either terminate the chain or else call chain.doFilter() to pass control on to the next filter:

public class AuthenticatingFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
      throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (!"MyAuthToken".equals(httpRequest.getHeader("X-Auth-Token")) {
             return;
        }
        chain.doFilter(request, response);
    }
}

3. Command

The Command pattern allows us to encapsulate some concrete behaviors – or commands – behind a common interface, such that they can be correctly triggered at runtime.

Typically we'll have a Command interface, a Receiver instance that receives the command instance, and an Invoker that is responsible for calling the correct command instance. We can then define different instances of our Command interface to perform different actions on the receiver:

interface DoorCommand {
    perform(Door door);
}
class OpenDoorCommand implements DoorCommand {
    public void perform(Door door) {
        door.setState("open");
    }
}

Here, we have a command implementation that will take a Door as the receiver and will cause the door to become “open”. Our invoker can then call this command when it wishes to open a given door, and the command encapsulates how to do this.

In the future, we might need to change our OpenDoorCommand to check that the door isn't locked first. This change will be entirely within the command, and the receiver and invoker classes don't need to have any changes.

3.1. Examples in the JVM

A very common example of this pattern is the Action class within Swing:

Action saveAction = new SaveAction();
button = new JButton(saveAction)

Here, SaveAction is the command, the Swing JButton component that uses this class is the invoker, and the Action implementation is called with an ActionEvent as the receiver.

4. Iterator

The Iterator pattern allows us to work across the elements in a collection and interact with each in turn. We use this to write functions taking an arbitrary iterator over some elements without regard to where they are coming from. The source could be an ordered list, an unordered set, or an infinite stream:

void printAll<T>(Iterator<T> iter) {
    while (iter.hasNext()) {
        System.out.println(iter.next());
    }
}

4.1. Examples in the JVM

All of the JVM standard collections implement the Iterator pattern by exposing an iterator() method that returns an Iterator<T> over the elements in the collection. Streams also implement the same method, except in this case, it might be an infinite stream, so the iterator might never terminate.

5. Memento

The Memento pattern allows us to write objects that are able to change state, and then revert back to their previous state. Essentially an “undo” function for object state.

This can be implemented relatively easily by storing the previous state any time a setter is called:

class Undoable {
    private String value;
    private String previous;
    public void setValue(String newValue) {
        this.previous = this.value;
        this.value = newValue;
    }
    public void restoreState() {
        if (this.previous != null) {
            this.value = this.previous;
            this.previous = null;
        }
    }
}

This gives the ability to undo the last change that was made to the object.

This is often implemented by wrapping the entire object state in a single object, known as the Memento. This allows the entire state to be saved and restored in a single action, instead of having to save every field individually.

5.1. Examples in the JVM

JavaServer Faces provides an interface called StateHolder that allows implementers to save and restore their state. There are several standard components that implement this, consisting of individual components – for example, HtmlInputFile, HtmlInputText, or HtmlSelectManyCheckbox – as well as composite components such as HtmlForm.

6. Observer

The Observer pattern allows for an object to indicate to others that changes have happened. Typically we'll have a Subject – the object emitting events, and a series of Observers – the objects receiving these events. The observers will register with the subject that they want to be informed about changes. Once this has happened, any changes that happen in the subject will cause the observers to be informed:

class Observable {
    private String state;
    private Set<Consumer<String>> listeners = new HashSet<>;
    public void addListener(Consumer<String> listener) {
        this.listeners.add(listener);
    }
    public void setState(String newState) {
        this.state = state;
        for (Consumer<String> listener : listeners) {
            listener.accept(newState);
        }
    }
}

This takes a set of event listeners and calls each one every time the state changes with the new state value.

6.1. Examples in the JVM

Java has a standard pair of classes that allow us to do exactly this – java.beans.PropertyChangeSupport and java.beans.PropertyChangeListener.

PropertyChangeSupport acts as a class that can have observers added and removed from it and can notify them all of any state changes. PropertyChangeListener is then an interface that our code can implement to receive any changes that have happened:

PropertyChangeSupport observable = new PropertyChangeSupport();
// Add some observers to be notified when the value changes
observable.addPropertyChangeListener(evt -> System.out.println("Value changed: " + evt));
// Indicate that the value has changed and notify observers of the new value
observable.firePropertyChange("field", "old value", "new value");

Note that there are another pair of classes that seem a better fit – java.util.Observer and java.util.Observable. These are deprecated in Java 9 though, due to being inflexible and unreliable.

7. Strategy

The Strategy pattern allows us to write generic code and then plug specific strategies into it to give us the specific behavior needed for our exact cases.

This will typically be implemented by having an interface representing the strategy. The client code is then able to write concrete classes implementing this interface as needed for the exact cases. For example, we might have a system where we need to notify end-users and implement the notification mechanisms as pluggable strategies:

interface NotificationStrategy {
    void notify(User user, Message message);
}
class EmailNotificationStrategy implements NotificationStrategy {
    ....
}
class SMSNotificationStrategy implements NotificationStrategy {
    ....
}

We can then decide at runtime exactly which of these strategies to actually use to send this message to this user. We can also write new strategies to use with minimal impact on the rest of the system.

7.1. Examples in the JVM

The standard Java libraries use this pattern extensively, often in ways that may not seem obvious at first. For example, the Streams API introduced in Java 8 makes extensive use of this pattern. The lambdas provided to map(), filter(), and other methods are all pluggable strategies that are provided to the generic method.

Examples go back even further, though. The Comparator interface introduced in Java 1.2 is a strategy that can be provided to sort elements within a collection as required. We can provide different instances of the Comparator to sort the same list in different ways as desired:

// Sort by name
Collections.sort(users, new UsersNameComparator());
// Sort by ID
Collections.sort(users, new UsersIdComparator());

8. Template Method

The Template Method pattern is used when we want to orchestrate several different methods working together. We'll define a base class with the template method and a set of one or more abstract methods – either unimplemented or else implemented with some default behavior. The template method then calls these abstract methods in a fixed pattern. Our code then implements a subclass of this class and implements these abstract methods as needed:

class Component {
    public void render() {
        doRender();
        addEventListeners();
        syncData();
    }
    protected abstract void doRender();
    protected void addEventListeners() {}
    protected void syncData() {}
}

Here, we have some arbitrary UI component. Our subclasses will implement the doRender() method to actually render the component. We can also optionally implement the addEventListeners() and syncData() methods. When our UI framework renders this component it will guarantee that all three get called in the correct order.

8.1. Examples in the JVM

The AbstractList, AbstractSet, and AbstractMap used by Java Collections have many examples of this pattern. For example, the indexOf() and lastIndexOf() methods both work in terms of the listIterator() method, which has a default implementation but which gets overridden in some subclasses. Equally, the add(T) and addAll(int, T) methods both work in terms of the add(int, T) method which doesn't have a default implementation and needs to be implemented by the subclass.

Java IO also makes use of this pattern within InputStream, OutputStream, Reader, and Writer. For example, the InputStream class has several methods that work in terms of read(byte[], int, int), which needs the subclass to implement.

9. Visitor

The Visitor pattern allows our code to handle various subclasses in a typesafe way, without needing to resort to instanceof checks. We'll have a visitor interface with one method for each concrete subclass that we need to support. Our base class will then have an accept(Visitor) method. The subclasses will each call the appropriate method on this visitor, passing itself in. This then allows us to implement concrete behaviour in each of these methods, each knowing that it will be working with the concrete type:

interface UserVisitor<T> {
    T visitStandardUser(StandardUser user);
    T visitAdminUser(AdminUser user);
    T visitSuperuser(Superuser user);
}
class StandardUser {
    public <T> T accept(UserVisitor<T> visitor) {
        return visitor.visitStandardUser(this);
    }
}

Here we have our UserVisitor interface with three different visitor methods on it. Our example StandardUser calls the appropriate method, and the same will be done in AdminUser and Superuser. We can then write our visitors to work with these as needed:

class AuthenticatingVisitor {
    public Boolean visitStandardUser(StandardUser user) {
        return false;
    }
    public Boolean visitAdminUser(AdminUser user) {
        return user.hasPermission("write");
    }
    public Boolean visitSuperuser(Superuser user) {
        return true;
    }
}

Our StandardUser never has permission, our Superuser always has permission, and our AdminUser might have permission but this needs to be looked up in the user itself.

9.1. Examples in the JVM

The Java NIO2 framework uses this pattern with Files.walkFileTree(). This takes an implementation of FileVisitor that has methods to handle various different aspects of walking the file tree. Our code can then use this for searching files, printing out matching files, processing many files in a directory, or lots of other things that need to work within a directory:

Files.walkFileTree(startingDir, new SimpleFileVisitor() {
    public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
        System.out.println("Found file: " + file);
    }
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        System.out.println("Found directory: " + dir);
    }
});

10. Conclusion

In this article, we've had a look at various design patterns used for the behaviour of objects. We've also looked at examples of these patterns as used within the core JVM as well, so we can see them in use in a way that many applications already benefit from.

The post Behavioral Patterns in Core Java first appeared on Baeldung.

        

Viewing all articles
Browse latest Browse all 4536

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>