Design pattern behavioral pattern

Behavioral model

The behavior model is responsible for efficient communication and responsibility assignment between objects.

Behavioral model:

  • Policy pattern: encapsulates a set of algorithms into a series of objects. By passing these objects, you can flexibly change the function of the program. For example: Java util. Comparator#compare()
  • Template method pattern: let subclasses override part of the method instead of the whole method. You can control which operations subclasses need to override. For example: Java util. Collections#sort()
  • Observer mode: it allows an object to flexibly send messages to interested objects. For example, Java util. EventListener
  • Iterator pattern: provides a consistent method to access the objects in the collection in sequence, which is independent of the specific implementation of the underlying collection. For example: Java util. Iterator
  • Responsibility chain mode: by transferring the request from one object to the next object in the chain until the request is processed, so as to realize the decoupling between objects. For example: Java util. logging. Logger#log()
  • Command mode: encapsulate operations into objects for storage, transfer and return. For example: Java lang.Runnable
  • Memo mode: generates a snapshot of the object's state so that the object can return to its original state without exposing its content. For example, the Date object implements the memo pattern through its own long value.
  • State mode: by changing the internal state of an object, you can dynamically change the behavior of an object at runtime. For example: Java util. Iterator
  • Visitor mode: provides a convenient and maintainable way to manipulate a group of objects. It allows you to modify or expand the behavior of the object without changing the object of the operation. For example: javax lang.model. element. Element and javax. lang.model. element. ElementVisitor
  • Mediator pattern: by using an intermediate object to distribute messages and reduce direct dependencies between classes. For example, Java util. concurrent. Executor#execute()
  • Interpreter mode: defines the syntax of a language, and then parses the statements of the corresponding syntax. For example: Java util. Pattern

Responsibility chain model

intention

Allows you to send requests along the handler chain. After receiving the request, each handler can process the request or pass it to the next handler in the chain.

problem

Suppose you are developing an online ordering system. You want to restrict system access and allow only authenticated users to create orders. In addition, users with administrative rights also have full access to all orders.

After simple planning, you will realize that these inspections must be carried out in turn. As long as a request containing user credentials is received, the application can attempt to authenticate the user entering the system. However, if the authentication fails due to incorrect user credentials, there is no need for subsequent inspection.

Requests must go through a series of checks before they can be processed by the ordering system.

In the next few months, your inspection has been realized.

  • A colleague believes that there is a potential safety hazard in directly transmitting the original data to the ordering system. Therefore, you have added additional verification steps to clean up the data in the request.
  • After some time, it was noted that the system could not resist the attack of violent password cracking. To prevent this situation, you immediately added a check step to filter repeated wrong requests from the same IP address.
  • It is also suggested that you can return the results in the cache for repeated requests containing the same data, so as to improve the response speed of the system. Therefore, you have added a check step to ensure that the request can pass and be sent to the system only if there is no qualified cache result.

The code is getting more and more chaotic.

The check code is already messy, and each new function will make it more bloated. Modifying one inspection step sometimes affects other inspection steps. Worst of all, when you want to reuse these inspection steps to protect other system components, you can only copy part of the code, because these components only need some rather than all of the inspection steps.

The system will become very confusing and its maintenance costs will soar. After a hard time with this code, one day you finally decide to refactor the whole system.

Solution

Like many other behavior design patterns, the chain of responsibility transforms a particular behavior into a separate object called a handler. In the above example, each check step can be extracted as a class with only a single method and perform the check operation. The request and its data are passed to the method as parameters.

The pattern suggests that you connect these processors into a chain. Each handler in the chain has a member variable to hold a reference to the next handler. In addition to processing the request, the handler is also responsible for passing the request along the chain. The request moves along the chain until all processors have the opportunity to process it.

Most importantly, the processor can decide not to pass the request along the chain, which can efficiently cancel all subsequent processing steps.

In our ordering system example, the handler will decide whether to continue to pass the request along the chain after processing the request. If the request contains the correct data, all processors will perform their main behavior, whether it is authentication or data caching.

The processors are arranged in turn to form a chain.

However, there is a slightly different way (and a more classic way), that is, after receiving the request, the processor decides whether it can be processed. If you can handle it yourself, the handler will not continue to deliver the request. In this case, there is either no handler or no handler for each request. This is very common when dealing with events in the graphical user interface element stack.

For example, when a user clicks a button, the events generated by the button will be passed along the GUI element chain, starting with the container of the button (such as a form or panel) to the main window of the application. The first element in the chain that can handle this event will handle it. In addition, there is another aspect of this example that deserves our attention: it shows that we can always extract chains from the object tree.

The branches of the object tree can form a chain.

The key is that all handler classes implement the same interface. Each specific handler only cares about the next handler that contains the execute execution method. In this way, you can use different processors to create chains at run time without coupling the relevant code with the processor's specific classes.

Real world analogy

When you call technical support, you may have to deal with multiple listeners.

Recently, you just bought and installed a new hardware device for your computer. As a geek, you obviously have multiple operating systems installed on your computer, so you will try to start all operating systems to confirm whether they support new hardware devices. Windows detected the hardware device and enabled it automatically. But your favorite Linux system doesn't support new hardware devices. Finally, I'll call you with a box of technical support.

First, you will hear the machine synthesized voice of the automatic responder. It provides nine common solutions to various problems, but none of them is related to the problem you encounter. After a while, the robot will transfer you to the human listener.

The listener was also unable to provide any specific solutions. He constantly quotes the lengthy content in the manual and doesn't listen carefully to your response. The 10th time I heard "do you want to shut down your computer and restart it?" After that, you ask to talk to a real engineer.

Finally, the person who answered the call transferred your call to the engineer. He may be huddled in the dark basement of an office building, sitting in his beloved server room, anxiously looking forward to communicating with a real person. The engineer told you the download website of the new hardware device driver and how to install it on Linux system. The problem is finally solved! You hung up with joy.

Pattern structure

The Handler declares a common interface for all specific handlers. This interface usually contains only a single method for request processing, but sometimes it also contains a method to set the next Handler in the chain.

Base Handler is an optional class in which you can place the sample code shared by all handlers.

Typically, this class defines a member variable that holds a reference to the next handler. The client can create the chain by passing the handler to the constructor or setting method of the previous handler. This class can also implement the default processing behavior: determine the existence of the next handler before passing the request to it.

Concrete Handlers contain the actual code that handles the request. After receiving the request, each handler must decide whether to process it and whether to pass the request along the chain.

The processor is usually independent and immutable, and needs to obtain all necessary data at one time through the constructor.

The Client can generate the chain at one time or dynamically according to the program logic. It is worth noting that the request can be sent to any processor in the chain, rather than the first processor.

General code implementation

public class ChainPatternDemo {
   
   public static void main(String[] args) {
      // Business process 1
      Handler thirdHandler = new Handler3(null);
      Handler secondHandler = new Handler2(thirdHandler);
      Handler firstHandler = new Handler1(secondHandler);
      firstHandler.execute();
      
      // Business process 2
      thirdHandler = new Handler3(null);
      secondHandler = new Handler1(thirdHandler);
      firstHandler = new Handler2(secondHandler);
      firstHandler.execute();
   }
   
   public static abstract class Handler {
      
      protected Handler successor;
      
      public Handler(Handler successor) {
         this.successor = successor;
      }
      
      public abstract void execute();
      
   }
   
   public static class Handler1 extends Handler {
      
      public Handler1(Handler successor) {
         super(successor);
      }
      
      public void execute() {
         System.out.println("Executive function 1");  
         if(successor != null) {
            successor.execute();
         }
      }
      
   }
   
   public static class Handler2 extends Handler {
      
      public Handler2(Handler successor) {
         super(successor);
      }
      
      public void execute() {
         System.out.println("Executive function 2");  
         if(successor != null) {
            successor.execute();
         }
      }
      
   }
   
   public static class Handler3 extends Handler {
      
      public Handler3(Handler successor) {
         super(successor);
      }
      
      public void execute() {
         System.out.println("Executive function 3");  
         if(successor != null) {
            successor.execute();
         }
      }
      
   }
   
}

Filter access

Usage example: the responsibility chain pattern is not common in Java programs because it works only when the code deals with the object chain.

One of the most popular use cases of this pattern is to pass events up to the parent component in a GUI class. Another notable use case is the sequential access filter.

This example shows how requests containing user data perform various behaviors (such as authentication, authorization, and authentication) through the processor chain in turn.

This example is somewhat different from the standard version of the pattern given by many authors. Most pattern examples look for the right handler and exit the chain after processing. But here we will execute each handler until a handler cannot process the request. Note that although the process is slightly different, this is still the responsibility chain model.

Basic verification interface

/**
 * Base middleware class.
 */
public abstract class Middleware {
    private Middleware next;

    /**
     * Builds chains of middleware objects.
     */
    public Middleware linkWith(Middleware next) {
        this.next = next;
        return next;
    }

    /**
     * Subclasses will implement this method with concrete checks.
     */
    public abstract boolean check(String email, String password);

    /**
     * Runs check on the next object in chain or ends traversing if we're in
     * last object in chain.
     */
    protected boolean checkNext(String email, String password) {
        if (next == null) {
            return true;
        }
        return next.check(email, password);
    }
}

Check request quantity limit

/**
 * ConcreteHandler. Checks whether there are too many failed login requests.
 */
public class ThrottlingMiddleware extends Middleware {
    private int requestPerMinute;
    private int request;
    private long currentTime;

    public ThrottlingMiddleware(int requestPerMinute) {
        this.requestPerMinute = requestPerMinute;
        this.currentTime = System.currentTimeMillis();
    }

    /**
     * Please, not that checkNext() call can be inserted both in the beginning
     * of this method and in the end.
     *
     * This gives much more flexibility than a simple loop over all middleware
     * objects. For instance, an element of a chain can change the order of
     * checks by running its check after all other checks.
     */
    public boolean check(String email, String password) {
        if (System.currentTimeMillis() > currentTime + 60_000) {
            request = 0;
            currentTime = System.currentTimeMillis();
        }

        request++;
        
        if (request > requestPerMinute) {
            System.out.println("Request limit exceeded!");
            Thread.currentThread().stop();
        }
        return checkNext(email, password);
    }
}

Check user login information

/**
 * ConcreteHandler. Checks whether a user with the given credentials exists.
 */
public class UserExistsMiddleware extends Middleware {
    private Server server;

    public UserExistsMiddleware(Server server) {
        this.server = server;
    }

    public boolean check(String email, String password) {
        if (!server.hasEmail(email)) {
            System.out.println("This email is not registered!");
            return false;
        }
        if (!server.isValidPassword(email, password)) {
            System.out.println("Wrong password!");
            return false;
        }
        return checkNext(email, password);
    }
}

Check user roles

/**
 * ConcreteHandler. Checks a user's role.
 */
public class RoleCheckMiddleware extends Middleware {
    public boolean check(String email, String password) {
        if (email.equals("admin@example.com")) {
            System.out.println("Hello, admin!");
            return true;
        }
        System.out.println("Hello, user!");
        return checkNext(email, password);
    }
}

Authorization target

/**
 * Server class.
 */
public class Server {
    private Map<String, String> users = new HashMap<>();
    private Middleware middleware;

    /**
     * Client passes a chain of object to server. This improves flexibility and
     * makes testing the server class easier.
     */
    public void setMiddleware(Middleware middleware) {
        this.middleware = middleware;
    }

    /**
     * Server gets email and password from client and sends the authorization
     * request to the chain.
     */
    public boolean logIn(String email, String password) {
        if (middleware.check(email, password)) {
            System.out.println("Authorization have been successful!");

            // Do something useful here for authorized users.

            return true;
        }
        return false;
    }

    public void register(String email, String password) {
        users.put(email, password);
    }

    public boolean hasEmail(String email) {
        return users.containsKey(email);
    }

    public boolean isValidPassword(String email, String password) {
        return users.get(email).equals(password);
    }
}

Client code

/**
 * Demo class. Everything comes together here.
 */
public class Demo {
    private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    private static Server server;

    private static void init() {
        server = new Server();
        server.register("admin@example.com", "admin_pass");
        server.register("user@example.com", "user_pass");

        // All checks are linked. Client can build various chains using the same
        // components.
        Middleware middleware = new ThrottlingMiddleware(2);
        middleware.linkWith(new UserExistsMiddleware(server))
                .linkWith(new RoleCheckMiddleware());

        // Server gets a chain from client code.
        server.setMiddleware(middleware);
    }

    public static void main(String[] args) throws IOException {
        init();

        boolean success;
        do {
            System.out.print("Enter email: ");
            String email = reader.readLine();
            System.out.print("Input password: ");
            String password = reader.readLine();
            success = server.logIn(email, password);
        } while (!success);
    }
}

Application scenario of pattern

1. When the program needs to handle different kinds of requests in different ways, and the type and order of requests are unknown in advance, the responsibility chain mode can be used.

Patterns can connect multiple processors into a chain. When a request is received, it "asks" each handler if they can process it. In this way, all processors have the opportunity to process the request.

2. This mode can be used when multiple processors must be executed sequentially.

No matter what order you connect processors into a chain, all requests will pass through the processors in the chain in strict order.

3. If the required processors and their order must be changed at run time, the chain of responsibility pattern can be used.

If there is a setting method for reference member variables in the handler class, you will be able to dynamically insert and remove handlers, or change their order.

There are two situations in the responsibility chain mode:

  • Pure responsibility chain mode: a request must be received by a handler object, and a specific handler can only adopt one of the following two behaviors for processing a request: self processing (assuming responsibility); Shift the responsibility to the next family.
  • Impure responsibility chain mode: it allows a specific handler object to transfer the remaining responsibility to the next home after assuming part of the responsibility of the request, and a request can not be received by any receiving end object.

Example of responsibility chain pattern in JDK

  • javax.servlet.Filter#doFilter()
  • java.util.logging.Logger#log()

Relationship with other modes

  • Responsibility chain mode, command mode, mediator mode and observer mode are used to handle different connection modes between request sender and receiver:

    • The responsibility chain dynamically passes the request to a series of potential recipients in sequence until one of the recipients processes the request.
    • The command establishes a one-way connection between the sender and the requester.
    • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
    • Observers allow recipients to dynamically subscribe to or cancel receiving requests.
  • The chain of responsibility is usually used in combination with the combination model. In this case, after receiving the request, the leaf component can pass the request along the chain containing all the parent components to the bottom of the object tree.

  • The manager of the responsibility chain can use the command mode. In this case, you can perform many different operations on the same context object represented by the request.

    There is another implementation, that is, the request itself is a command object. In this case, you can perform the same operation on a chain connected by a series of different contexts.

  • The class structure of responsibility chain and decoration pattern is very similar. Both rely on recursive composition to pass the operation to be performed to a series of objects. However, there are several important differences between the two.

    The managers of the responsibility chain can perform all operations independently of each other, and can stop transmitting requests at any time. On the other hand, various decorations can extend the behavior of objects while following the basic interface. In addition, the decoration cannot interrupt the delivery of the request.

Command mode

intention

It can transform the request into a separate object that contains all the information related to the request. This transformation allows you to parameterize the method according to different requests, delay the execution of requests or put them in the queue, and implement revocable operations.

problem

If you are developing a new text editor, the current task is to create a toolbar containing multiple buttons, and let each button correspond to different operations of the editor. You have created a very concise button class, which can be used not only to generate buttons on the toolbar, but also to generate general buttons for various dialog boxes.

All buttons in the application can inherit the same class

Although all buttons look similar, they can perform different operations (open, save, print, apply, etc.). Where would you place the click processing code for these buttons? The simplest solution is to create a large number of subclasses everywhere buttons are used. These subclasses contain the code that must be executed after the button is clicked.

A large number of button subclasses. irrespective.

You soon realize that this approach has serious flaws. First, you have created a large number of subclasses. Every time you modify the base class button, you may need to modify the code of all subclasses. Simply put, GUI code relies on unstable code in business logic in a clumsy way.

Multiple classes implement the same function.

Another part is the most difficult. Operations such as copy / paste text may be called in multiple places. For example, users can click the small "copy" button on the toolbar, or copy some content through the context menu, or directly use Ctrl+C on the keyboard.

Our program originally had only toolbars, so we can use the Button subclass to realize various operations. In other words, the Copy Button Copy ­ It is feasible for the Button subclass to contain code that copies text. After implementing context menus, shortcuts and other functions, you either need to Copy the operation code into many classes, or you need to make the menu dependent on buttons, which is a worse choice.

Solution

Good software design usually separates concerns, which often leads to software layering. The most common examples: the first layer is responsible for the user image interface; The other layer is responsible for business logic. The GUI layer is responsible for rendering beautiful graphics on the screen, capturing all inputs and displaying the results of user and program work. When some important content needs to be completed (such as calculating the lunar orbit or writing an annual report), the GUI layer will delegate the work to the bottom of the business logic.

This looks like this in the code: a GUI object passes some parameters to call a business logic object. This process is usually described as one object sending a request to another object.

The GUI layer can directly access the business logic layer.

Command mode recommends that GUI objects do not submit these requests directly. You should extract all the details of the request (such as the called object, method name and parameter list) to form a command class, which contains only one method used to trigger the request.

The command object is responsible for connecting different GUI and business logic objects. After that, the GUI object does not need to know whether the business logic object has obtained the request or how it processes the request. The GUI object can trigger the command, and the command object will handle all the details by itself.

Access the business logic layer through commands.

The next step is to make all commands implement the same interface. This interface usually has only one execution method without any parameters, so that you can use the same request sender to execute different commands without coupling with specific command classes. In addition, there are additional benefits. Now you can change the behavior of the sender by switching the command object connected to the sender at run time.

You may notice a missing piece of the puzzle - the requested parameters. GUI objects can provide some parameters to business layer objects. But the method of executing the command does not have any parameters, so how can we send the details of the request to the receiver? The answer is: pre configure the command with data, or let it get the data by itself.

GUI objects delegate commands to command objects.

Let's go back to the text editor. After applying the command mode, we no longer need any Button subclass to realize the click behavior. We just need to add a member variable to the Button base class to store the reference to the command object, and execute the command after clicking.

You need to implement a series of command classes for each possible operation, and connect the command and button according to the required behavior of the button.

GUI elements such as other menus, shortcuts or the whole dialog box can be implemented in the same way. When the user interacts with GUI elements, the commands connected to them will be executed. Now you've probably guessed that elements related to the same operation will be connected to the same command, thus avoiding duplication of code.

Finally, commands become an intermediate layer to reduce the coupling between GUI and business logic layer. This is only a small part of the benefits provided by command mode!

Real world analogy

Order in the restaurant.

After a long walk in the city center, you find a good restaurant and sit on the window seat. A friendly waiter approaches you and quickly notes down your order on a piece of paper. The waiter came to the kitchen and pasted the order on the wall. After a while, the cook got the order. He prepared the food according to the order. The cook put the prepared food on the tray together with the order. After seeing the tray, the waiter checks the order to make sure that all the food is what you want, and then puts the food on your table.

That piece of paper is an order. It's in the queue until the cook starts cooking. The command contains all the information related to cooking these foods. The chef can start cooking immediately according to it without running to confirm the order details directly with you.

Pattern structure

The Sender -- also known as the "Invoker" -- class is responsible for initializing the request, which must contain a member variable to store a reference to the command object. The Sender triggers the command without sending the request directly to the receiver. Note that the Sender is not responsible for creating the command object: it usually gets the pre generated command from the client through the constructor.

A Command interface usually declares only one method of executing a Command.

Concrete Commands implement various types of requests. The specific command itself does not complete the work, but delegates the call to a business logic object. But to simplify the code, these classes can be merged.

The parameters required by the receiving object to execute the method can be declared as member variables of specific commands. You can make the command object immutable and only allow initialization of these member variables through the constructor.

The Receiver class contains some business logic. Almost any object can be a recipient. Most commands only deal with the details of how to pass the request to the Receiver, who will complete the actual work.

The Client creates and configures specific command objects. The Client must pass all request parameters, including the receiver entity, to the constructor of the command. After that, the generated command can be associated with one or more senders.

General code implementation

public class CommandPatternDemo {
   
   public static void main(String[] args) {
      Command commandA = new CommandA();
      Command commandB = new CommandB();
      
      Invoker invoker = new Invoker();
      
      invoker.setCommand(commandA); 
      invoker.execute();
      
      invoker.setCommand(commandB);
      invoker.execute();
   }
   
   public interface Command {
      void execute();
   }
   
   public static class CommandA implements Command {
      
      @Override
      public void execute() {
         System.out.println("command A Functional logic of");  
      }
      
   }
   
   public static class CommandB implements Command {
      
      @Override
      public void execute() {
         System.out.println("command B Functional logic of"); 
      }
      
   }
   
   public static class Invoker {
      
      private Command command;
      
      public Command getCommand() {
         return command;
      }

      public void setCommand(Command command) {
         this.command = command;
      }

      public void execute() {
         System.out.println("Some other logic A");
         command.execute();
         System.out.println("Some other logic B");
      } 
      
   }
   
}

Text editor and undo

Usage example: command mode is very common in Java code. In most cases, it is used to replace the callback function of parameterized UI elements containing behavior. In addition, it is also used to sort tasks and record operation history.

The text editor in this example creates a new command object every time a user interacts with it. After the command executes its behavior, it is pushed into the history stack.

Now, when the program performs the undo operation, it needs to take the most recently executed command from the history record, and then perform the reverse operation or restore the editor history state saved by the command.

Abstract basic command

public abstract class Command {
    public Editor editor;
    private String backup;

    Command(Editor editor) {
        this.editor = editor;
    }

    void backup() {
        backup = editor.textField.getText();
    }

    public void undo() {
        editor.textField.setText(backup);
    }

    public abstract boolean execute();
}

Copies the selected text to the clipboard

public class CopyCommand extends Command {

    public CopyCommand(Editor editor) {
        super(editor);
    }

    @Override
    public boolean execute() {
        editor.clipboard = editor.textField.getSelectedText();
        return false;
    }
}

Paste text from clipboard

public class PasteCommand extends Command {

    public PasteCommand(Editor editor) {
        super(editor);
    }

    @Override
    public boolean execute() {
        if (editor.clipboard == null || editor.clipboard.isEmpty()) return false;

        backup();
        editor.textField.insert(editor.clipboard, editor.textField.getCaretPosition());
        return true;
    }
}

Cut text to clipboard

public class CutCommand extends Command {

    public CutCommand(Editor editor) {
        super(editor);
    }

    @Override
    public boolean execute() {
        if (editor.textField.getSelectedText().isEmpty()) return false;

        backup();
        String source = editor.textField.getText();
        editor.clipboard = editor.textField.getSelectedText();
        editor.textField.setText(cutString(source));
        return true;
    }

    private String cutString(String source) {
        String start = source.substring(0, editor.textField.getSelectionStart());
        String end = source.substring(editor.textField.getSelectionEnd());
        return start + end;
    }
}

Command history

public class CommandHistory {
    private Stack<Command> history = new Stack<>();

    public void push(Command c) {
        history.push(c);
    }

    public Command pop() {
        return history.pop();
    }

    public boolean isEmpty() { return history.isEmpty(); }
}

GUI for text editor

public class Editor {
    public JTextArea textField;
    public String clipboard;
    private CommandHistory history = new CommandHistory();

    public void init() {
        JFrame frame = new JFrame("Text editor (type & use buttons, Luke!)");
        JPanel content = new JPanel();
        frame.setContentPane(content);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
        textField = new JTextArea();
        textField.setLineWrap(true);
        content.add(textField);
        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
        JButton ctrlC = new JButton("Ctrl+C");
        JButton ctrlX = new JButton("Ctrl+X");
        JButton ctrlV = new JButton("Ctrl+V");
        JButton ctrlZ = new JButton("Ctrl+Z");
        Editor editor = this;
        ctrlC.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                executeCommand(new CopyCommand(editor));
            }
        });
        ctrlX.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                executeCommand(new CutCommand(editor));
            }
        });
        ctrlV.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                executeCommand(new PasteCommand(editor));
            }
        });
        ctrlZ.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                undo();
            }
        });
        buttons.add(ctrlC);
        buttons.add(ctrlX);
        buttons.add(ctrlV);
        buttons.add(ctrlZ);
        content.add(buttons);
        frame.setSize(450, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void executeCommand(Command command) {
        if (command.execute()) {
            history.push(command);
        }
    }

    private void undo() {
        if (history.isEmpty()) return;

        Command command = history.pop();
        if (command != null) {
            command.undo();
        }
    }
}

Client code

public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.init();
    }
}

Application scenario of pattern

1. If you need to parameterize objects through operations, you can use command mode.

Command mode can convert specific method calls into independent objects. This change also brings many interesting applications: you can pass commands as parameters of methods, save commands in other objects, or switch connected commands at run time.

For example: you are developing a GUI component (such as context menu). You want users to be able to configure menu items and trigger actions when they click on menu items.

2. If you want to queue operations, execute operations, or perform operations remotely, you can use command mode.

Like other objects, commands can also be serialized (serialization means converting to strings), so that they can be easily written to files or databases. After a period of time, the string can be restored to the original command object. Therefore, you can delay or plan the execution of the command. But its function is far more than that! You can queue commands or send commands through the network.

3. If you want to implement the operation rollback function, you can use the command mode.

Although there are many ways to implement undo and restore functions, the command mode is probably the most commonly used one.

In order to be able to roll back operations, you need to implement the history function of executed operations. Command history is a stack structure that contains all executed command objects and their related program state backups.

This method has two disadvantages. First, the saving function of program state is not easy to implement, because some states may be private. You can use memo mode to solve this problem to some extent.

Second, the backup state may take up a lot of memory. Therefore, sometimes you need to implement it in another way: the command does not need to restore the original state, but performs the reverse operation. Reverse operation also has a cost: it may be difficult or even impossible to achieve.

Example of command mode in JDK

  • java. All implementations of lang.runnable

  • javax. swing. All implementations of action

Relationship with other modes

  • Responsibility chain mode, command mode, mediator mode and observer mode are used to handle different connection modes between request sender and receiver:

    • The responsibility chain dynamically passes the request to a series of potential recipients in sequence until one of the recipients processes the request.
    • The command establishes a one-way connection between the sender and the requester.
    • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
    • Observers allow recipients to dynamically subscribe to or cancel receiving requests.
  • The manager of the responsibility chain can use the command mode. In this case, you can perform many different operations on the same context object represented by the request.

    There is another implementation, that is, the request itself is a command object. In this case, you can perform the same operation on a chain connected by a series of different contexts.

  • You can use both command and memo mode to "undo". In this case, the command is used to perform various operations on the target object, and the memo is used to save the state of the object before a command is executed.

  • Command and policy patterns look similar because both can parameterize objects through certain behaviors. However, their intentions are very different.

    • You can use commands to convert any operation into an object. The parameters of the operation become member variables of the object. You can use transformations to delay the execution of operations, queue operations, save historical commands, or send commands to remote services.

    • On the other hand, policies can often be used to describe different ways to accomplish something, allowing you to switch algorithms in the same context class.

  • Prototype mode can be used to save the history of commands.

  • You can think of visitor mode as an enhanced version of command mode. Its objects can perform operations on a variety of objects of different classes.

Iterator mode

intention

Allows you to traverse all elements of a collection without exposing the underlying representations of the collection (lists, stacks, trees, etc.).

problem

Set is one of the most commonly used data types in programming. Nevertheless, a collection is just a container for a set of objects.

Various types of collections.

Most collections use simple lists to store elements. But some collections also use stacks, trees, graphs, and other complex data structures.

No matter how a collection is constructed, it must provide some way to access elements so that other code can use them. A collection should provide a way to traverse elements and ensure that it does not repeatedly access the same element.

If your collection is based on a list, the job sounds simple. But how do you traverse elements in complex data structures, such as trees? For example, today you need to use the depth first algorithm to traverse the tree structure, and tomorrow you may need the breadth first algorithm; Next week, you may need other methods (such as random access to elements in the tree).

The same set can be traversed in different ways.

Constantly adding traversal algorithms to the collection will blur its main responsibility of "efficiently storing data". In addition, some algorithms may be customized according to specific applications, and it would be very strange to add them to generic collection classes.

On the other hand, client code that uses multiple collections may not care about how the data is stored. However, because collections provide different ways to access elements, your code will have to be coupled with specific collection classes.

Solution

The main idea of iterator pattern is to extract the traversal behavior of a collection into separate iterator objects.

Iterators can implement a variety of traversal algorithms. Multiple iterator objects can traverse the same collection at the same time.

In addition to implementing its own algorithm, the iterator encapsulates all the details of the traversal operation, such as the current position and the number of remaining elements at the end. Therefore, multiple iterators can access the collection independently of each other at the same time.

Iterators usually provide a basic way to get collection elements. The client can keep calling this method until it returns nothing, which means that the iterator has traversed all elements.

All iterators must implement the same interface. In this way, as long as there is a suitable iterator, the client code can be compatible with any type of collection or traversal algorithm. If you need to traverse the collection in a special way, just create a new iterator class without modifying the collection or the client.

Real world analogy

Different ways to walk around Rome.

You plan to visit Rome for a few days and visit all the major tourist attractions. But after arriving at your destination, you may waste a lot of time circling around and even can't find where the Roman Colosseum is.

Or you can buy a virtual tour guide on your smartphone. This program is very smart and inexpensive. You can stay at the scenic spot as long as you want.

The third option is to use part of the travel budget to hire a local guide who knows the city like the back of his hand. The guide can arrange the itinerary according to your preferences, introduce each scenic spot and tell many exciting stories. Such a trip may be more interesting, but it will cost more.

All of these options (free walk, smartphone navigation or live Guide) are iterators of this collection of Roman attractions.

Pattern structure

The Iterator interface declares the operations required to traverse the collection: get the next element, get the current position, restart the iteration, and so on.

Concrete Iterators implement a specific algorithm for traversing a set. The iterator object must track the progress of its traversal. This allows multiple iterators to traverse the same set independently of each other.

The Collection interface declares one or more methods to get iterators compatible with the Collection. Note that the type of the return method must be declared as an iterator interface, so a specific Collection can return various kinds of iterators.

Concrete Collections will return a specific concrete iterator class entity when the client requests an iterator. You might wonder, where is the rest of the set code? Don't worry, it will be in the same class. However, these details are not important for the actual model, so we have omitted them.

The Client interacts with both sets and iterators through their interfaces. In this way, the Client does not need to be coupled with specific classes, allowing the same Client code to use a variety of different collections and iterators.

The client usually does not create its own iterator, but gets it from the collection. However, in certain cases, the client can directly create an iterator (for example, when the client needs to customize a special iterator).

Iterative access to social networking profiles

Usage example: this pattern is common in Java code. Many frameworks and libraries use it to provide a standard way to traverse their collections.

In this example, the iterator pattern is used to access social media profiles in a remote social network collection without exposing communication details to client code.

Define file interface

public interface ProfileIterator {
    boolean hasNext();

    Profile getNext();

    void reset();
}

Iterating on Facebook profiles

public class FacebookIterator implements ProfileIterator {
    private Facebook facebook;
    private String type;
    private String email;
    private int currentPosition = 0;
    private List<String> emails = new ArrayList<>();
    private List<Profile> profiles = new ArrayList<>();

    public FacebookIterator(Facebook facebook, String type, String email) {
        this.facebook = facebook;
        this.type = type;
        this.email = email;
    }

    private void lazyLoad() {
        if (emails.size() == 0) {
            List<String> profiles = facebook.requestProfileFriendsFromFacebook(this.email, this.type);
            for (String profile : profiles) {
                this.emails.add(profile);
                this.profiles.add(null);
            }
        }
    }

    @Override
    public boolean hasNext() {
        lazyLoad();
        return currentPosition < emails.size();
    }

    @Override
    public Profile getNext() {
        if (!hasNext()) {
            return null;
        }

        String friendEmail = emails.get(currentPosition);
        Profile friendProfile = profiles.get(currentPosition);
        if (friendProfile == null) {
            friendProfile = facebook.requestProfileFromFacebook(friendEmail);
            profiles.set(currentPosition, friendProfile);
        }
        currentPosition++;
        return friendProfile;
    }

    @Override
    public void reset() {
        currentPosition = 0;
    }
}

Implement iteration on LinkedIn archives

public class LinkedInIterator implements ProfileIterator {
    private LinkedIn linkedIn;
    private String type;
    private String email;
    private int currentPosition = 0;
    private List<String> emails = new ArrayList<>();
    private List<Profile> contacts = new ArrayList<>();

    public LinkedInIterator(LinkedIn linkedIn, String type, String email) {
        this.linkedIn = linkedIn;
        this.type = type;
        this.email = email;
    }

    private void lazyLoad() {
        if (emails.size() == 0) {
            List<String> profiles = linkedIn.requestRelatedContactsFromLinkedInAPI(this.email, this.type);
            for (String profile : profiles) {
                this.emails.add(profile);
                this.contacts.add(null);
            }
        }
    }

    @Override
    public boolean hasNext() {
        lazyLoad();
        return currentPosition < emails.size();
    }

    @Override
    public Profile getNext() {
        if (!hasNext()) {
            return null;
        }

        String friendEmail = emails.get(currentPosition);
        Profile friendContact = contacts.get(currentPosition);
        if (friendContact == null) {
            friendContact = linkedIn.requestContactInfoFromLinkedInAPI(friendEmail);
            contacts.set(currentPosition, friendContact);
        }
        currentPosition++;
        return friendContact;
    }

    @Override
    public void reset() {
        currentPosition = 0;
    }
}

Define a common social networking interface

public interface SocialNetwork {
    ProfileIterator createFriendsIterator(String profileEmail);

    ProfileIterator createCoworkersIterator(String profileEmail);
}

Facebook

public class Facebook implements SocialNetwork {
    private List<Profile> profiles;

    public Facebook(List<Profile> cache) {
        if (cache != null) {
            this.profiles = cache;
        } else {
            this.profiles = new ArrayList<>();
        }
    }

    public Profile requestProfileFromFacebook(String profileEmail) {
        // Here would be a POST request to one of the Facebook API endpoints.
        // Instead, we emulates long network connection, which you would expect
        // in the real life...
        simulateNetworkLatency();
        System.out.println("Facebook: Loading profile '" + profileEmail + "' over the network...");

        // ...and return test data.
        return findProfile(profileEmail);
    }

    public List<String> requestProfileFriendsFromFacebook(String profileEmail, String contactType) {
        // Here would be a POST request to one of the Facebook API endpoints.
        // Instead, we emulates long network connection, which you would expect
        // in the real life...
        simulateNetworkLatency();
        System.out.println("Facebook: Loading '" + contactType + "' list of '" + profileEmail + "' over the network...");

        // ...and return test data.
        Profile profile = findProfile(profileEmail);
        if (profile != null) {
            return profile.getContacts(contactType);
        }
        return null;
    }

    private Profile findProfile(String profileEmail) {
        for (Profile profile : profiles) {
            if (profile.getEmail().equals(profileEmail)) {
                return profile;
            }
        }
        return null;
    }

    private void simulateNetworkLatency() {
        try {
            Thread.sleep(2500);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public ProfileIterator createFriendsIterator(String profileEmail) {
        return new FacebookIterator(this, "friends", profileEmail);
    }

    @Override
    public ProfileIterator createCoworkersIterator(String profileEmail) {
        return new FacebookIterator(this, "coworkers", profileEmail);
    }

}

Lingying

public class LinkedIn implements SocialNetwork {
    private List<Profile> contacts;

    public LinkedIn(List<Profile> cache) {
        if (cache != null) {
            this.contacts = cache;
        } else {
            this.contacts = new ArrayList<>();
        }
    }

    public Profile requestContactInfoFromLinkedInAPI(String profileEmail) {
        // Here would be a POST request to one of the LinkedIn API endpoints.
        // Instead, we emulates long network connection, which you would expect
        // in the real life...
        simulateNetworkLatency();
        System.out.println("LinkedIn: Loading profile '" + profileEmail + "' over the network...");

        // ...and return test data.
        return findContact(profileEmail);
    }

    public List<String> requestRelatedContactsFromLinkedInAPI(String profileEmail, String contactType) {
        // Here would be a POST request to one of the LinkedIn API endpoints.
        // Instead, we emulates long network connection, which you would expect
        // in the real life.
        simulateNetworkLatency();
        System.out.println("LinkedIn: Loading '" + contactType + "' list of '" + profileEmail + "' over the network...");

        // ...and return test data.
        Profile profile = findContact(profileEmail);
        if (profile != null) {
            return profile.getContacts(contactType);
        }
        return null;
    }

    private Profile findContact(String profileEmail) {
        for (Profile profile : contacts) {
            if (profile.getEmail().equals(profileEmail)) {
                return profile;
            }
        }
        return null;
    }

    private void simulateNetworkLatency() {
        try {
            Thread.sleep(2500);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public ProfileIterator createFriendsIterator(String profileEmail) {
        return new LinkedInIterator(this, "friends", profileEmail);
    }

    @Override
    public ProfileIterator createCoworkersIterator(String profileEmail) {
        return new LinkedInIterator(this, "coworkers", profileEmail);
    }
}

Social profile

public class Profile {
    private String name;
    private String email;
    private Map<String, List<String>> contacts = new HashMap<>();

    public Profile(String email, String name, String... contacts) {
        this.email = email;
        this.name = name;

        // Parse contact list from a set of "friend:email@gmail.com" pairs.
        for (String contact : contacts) {
            String[] parts = contact.split(":");
            String contactType = "friend", contactEmail;
            if (parts.length == 1) {
                contactEmail = parts[0];
            }
            else {
                contactType = parts[0];
                contactEmail = parts[1];
            }
            if (!this.contacts.containsKey(contactType)) {
                this.contacts.put(contactType, new ArrayList<>());
            }
            this.contacts.get(contactType).add(contactEmail);
        }
    }

    public String getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }

    public List<String> getContacts(String contactType) {
        if (!this.contacts.containsKey(contactType)) {
            this.contacts.put(contactType, new ArrayList<>());
        }
        return contacts.get(contactType);
    }
}

Messaging application

public class SocialSpammer {
    public SocialNetwork network;
    public ProfileIterator iterator;

    public SocialSpammer(SocialNetwork network) {
        this.network = network;
    }

    public void sendSpamToFriends(String profileEmail, String message) {
        System.out.println("\nIterating over friends...\n");
        iterator = network.createFriendsIterator(profileEmail);
        while (iterator.hasNext()) {
            Profile profile = iterator.getNext();
            sendMessage(profile.getEmail(), message);
        }
    }

    public void sendSpamToCoworkers(String profileEmail, String message) {
        System.out.println("\nIterating over coworkers...\n");
        iterator = network.createCoworkersIterator(profileEmail);
        while (iterator.hasNext()) {
            Profile profile = iterator.getNext();
            sendMessage(profile.getEmail(), message);
        }
    }

    public void sendMessage(String email, String message) {
        System.out.println("Sent message to: '" + email + "'. Message body: '" + message + "'");
    }
}

Client code

/**
 * Demo class. Everything comes together here.
 */
public class Demo {
    public static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        System.out.println("Please specify social network to target spam tool (default:Facebook):");
        System.out.println("1. Facebook");
        System.out.println("2. LinkedIn");
        String choice = scanner.nextLine();

        SocialNetwork network;
        if (choice.equals("2")) {
            network = new LinkedIn(createTestProfiles());
        }
        else {
            network = new Facebook(createTestProfiles());
        }

        SocialSpammer spammer = new SocialSpammer(network);
        spammer.sendSpamToFriends("anna.smith@bing.com",
                "Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?");
        spammer.sendSpamToCoworkers("anna.smith@bing.com",
                "Hey! This is Anna's boss Jason. Anna told me you would be interested in [link].");
    }

    public static List<Profile> createTestProfiles() {
        List<Profile> data = new ArrayList<Profile>();
        data.add(new Profile("anna.smith@bing.com", "Anna Smith", "friends:mad_max@ya.com", "friends:catwoman@yahoo.com", "coworkers:sam@amazon.com"));
        data.add(new Profile("mad_max@ya.com", "Maximilian", "friends:anna.smith@bing.com", "coworkers:sam@amazon.com"));
        data.add(new Profile("bill@microsoft.eu", "Billie", "coworkers:avanger@ukr.net"));
        data.add(new Profile("avanger@ukr.net", "John Day", "coworkers:bill@microsoft.eu"));
        data.add(new Profile("sam@amazon.com", "Sam Kitting", "coworkers:anna.smith@bing.com", "coworkers:mad_max@ya.com", "friends:catwoman@yahoo.com"));
        data.add(new Profile("catwoman@yahoo.com", "Liza", "friends:anna.smith@bing.com", "friends:sam@amazon.com"));
        return data;
    }
}

Application scenario of pattern

1. When there are complex data structures behind the collection and you want to hide their complexity from the client (for convenience or security), you can use the iterator mode.

Iterators encapsulate the details of interaction with complex data structures and provide clients with multiple simple ways to access collection elements. This method is not only very convenient for the client, but also can avoid the client from performing wrong or harmful operations when directly interacting with the collection, so as to protect the collection.

2. Using this mode can reduce the repeated traversal code in the program.

The code of important iterative algorithms is often very large. When these codes are placed in the business logic of the program, it will obscure the responsibilities of the original code and reduce its maintainability. Therefore, moving the traversal code to a specific iterator can make the program code more refined and concise.

3. If you want your code to traverse different or even unpredictable data structures, you can use the iterator pattern.

This pattern provides some common interfaces for collections and iterators. If you use these interfaces in your code, it will still work when you pass other collections and iterators that implement these interfaces to it.

Example of iterator pattern in JDK

  • java. util. All implementations of iterator (and java.util.Scanner).

  • java. util. All implementations of enumeration

Relationship with other modes

  • You can use the iterator pattern to traverse the composite pattern tree.

  • You can use both factory method patterns and iterators to make subclass collections return iterators of different types and match iterators to collections.

  • You can use both memo mode and iterator to get the current iterator state and roll back when needed.

  • You can use both visitor patterns and iterators to traverse complex data structures and perform the required operations on the elements in them, even if they belong to completely different classes.

Intermediary model

intention

It allows you to reduce the chaotic dependencies between objects. This pattern restricts the direct interaction between objects, forcing them to cooperate through a mediator object.

problem

If you have a dialog box for creating and modifying customer data, it consists of various controls, such as Text box ­ Field), Checkbox, Button, etc.

The relationship between the elements in the user interface will become chaotic with the development of the program.

Some form elements may interact directly. For example, selecting the "I have a dog" check box may display a hidden text box for entering the dog's name. Another example is that the submit button must verify all inputs before saving data.

There are many associations between elements. Therefore, modifications to some elements may affect others.

If you implement the business logic directly in the form element code, it will be difficult for you to reuse these element classes in other forms of the program. For example, because the check box class is coupled to a dog's text box, it will not be available in other forms. You can either use all the classes you use to render the data form, or none of them.

Solution

The mediator pattern recommends that you stop direct communication between components and make them independent of each other. These components must call a special mediator object, redirect the calling behavior through the mediator object, and cooperate in an indirect way. Ultimately, the component depends on only one mediator class and does not need to be coupled with multiple other components.

In the example of data editing form, the Dialog class itself will act as a mediator. It is likely that it knows all its child elements, so you don't even need to introduce new dependencies into this class.

UI elements must communicate indirectly through mediator objects.

Most of the important changes are made in the actual form elements. Let's think about the submit button. Previously, when the user clicks the button, it must verify the values of all form elements. Now its only job is to notify the dialog box of the click event. After receiving the notification, the dialog box can check the value itself or delegate the task to each element. In this way, the button is no longer associated with multiple form elements, but only depends on the dialog class.

You can also extract common interfaces for all types of dialog boxes to further weaken their dependencies. A notification method that can be used by all form elements will be declared in the interface, which can be used to notify the dialog box of events in the element. In this way, all dialog boxes that implement the interface can use the submit button.

In this way, the mediator pattern allows you to encapsulate a complex network of relationships between multiple objects in a single mediator object. The fewer dependencies a class has, the easier it is to modify, extend, or reuse.

Real world analogy

Aircraft pilots do not communicate with each other to decide the next aircraft to land. All communication is conducted through the control tower.

Aircraft pilots do not communicate directly with each other when approaching or leaving the air control area. But they will talk to the air traffic controller in the tower near the runway. If there is no air traffic controller, the pilot needs to pay attention to all aircraft near the airport and discuss the landing order with a committee composed of dozens of pilots. I'm afraid that will make the statistics of plane crashes soar.

The tower does not need to control the whole flight, but only needs to strengthen the control in the terminal area, because the number of decision-making participants in this area is too many for pilots.

Pattern structure

Components are various classes that contain business logic. Each Component has a reference to the mediator, which is declared as the mediator interface type. The Component does not know the class that the mediator actually belongs to, so you can connect it to different mediators so that it can be reused in other programs.

The Mediator interface declares the method of communicating with the component, but usually includes only one notification method. The component can take any context (including its own object) as the parameter of the method, so that there will be no coupling between the receiving component and the sender class.

Concrete Mediator encapsulates the relationship between various components. Specific mediators usually save and manage references to all components, and sometimes even manage their lifecycle.

The component does not know about other components. If an important event occurs within the component, it can only notify the mediator. After receiving the notification, the mediator can easily determine the sender, which may be enough to determine the components to be triggered next.

For components, the mediator looks like a complete black box. The sender doesn't know who will eventually process his request, and the receiver doesn't know who made the request in the first place.

General code implementation

public class MediatorPatternDemo {
   
   public static void main(String[] args) {
      Mediator mediator = new Mediator();
      
      ModuleA moduleA = new ModuleA(mediator);
      ModuleB moduleB = new ModuleB(mediator);
      ModuleC moduleC = new ModuleC(mediator);
      
      moduleA.execute();  
      moduleB.execute();  
      moduleC.execute();
   }
   
   public static class Mediator {
      
      private ModuleA moduleA;
      private ModuleB moduleB;
      private ModuleC moduleC;
      
      public ModuleA getModuleA() {
         return moduleA;
      }
      public void setModuleA(ModuleA moduleA) {
         this.moduleA = moduleA;
      }
      public ModuleB getModuleB() {
         return moduleB;
      }
      public void setModuleB(ModuleB moduleB) {
         this.moduleB = moduleB;
      }
      public ModuleC getModuleC() {
         return moduleC;
      }
      public void setModuleC(ModuleC moduleC) {
         this.moduleC = moduleC;
      }
      
      public void moduleAInvoke() {
         moduleB.execute("modular A Notify the intermediary");  
         moduleC.execute("modular A Notify the intermediary"); 
      }
      
      public void moduleBInvoke() {
         moduleA.execute("modular B Notify the intermediary");  
         moduleC.execute("modular B Notify the intermediary"); 
      }
      
      public void moduleCInvoke() {
         moduleA.execute("modular C Notify the intermediary");  
         moduleB.execute("modular C Notify the intermediary"); 
      }
      
   }
   
   public static class ModuleA {
      
      private Mediator mediator;
      
      public ModuleA(Mediator mediator) {
         this.mediator = mediator;
         this.mediator.setModuleA(this);
      }
      
      public void execute() {
         mediator.moduleAInvoke();
      }
      
      public void execute(String invoker) {
         System.out.println(invoker + "Calling module A Function of");
      }
      
   }
   
   public static class ModuleB {
      
      private Mediator mediator;
      
      public ModuleB(Mediator mediator) {
         this.mediator = mediator;
         this.mediator.setModuleB(this); 
      }
      
      public void execute() {
         mediator.moduleBInvoke();
      }
      
      public void execute(String invoker) {
         System.out.println(invoker + "Calling module B Function of");
      }
      
   }
   
   public static class ModuleC {
      
      private Mediator mediator;
      
      public ModuleC(Mediator mediator) {
         this.mediator = mediator;
         this.mediator.setModuleC(this);  
      }
      
      public void execute() {
         mediator.moduleCInvoke(); 
      }
      
      public void execute(String invoker) {
         System.out.println(invoker + "Calling module C Function of");
      }
      
   }
   
}

Note taking program

Usage example: the mediator mode is most commonly used in Java code to help the communication between GUI components of the program. In MVC mode, controller is synonymous with mediator.

This example shows how to organize many GUI elements so that they can cooperate without interdependence with the help of intermediaries.

Colleague classes

/**
 * Common component interface.
 */
public interface Component {
    void setMediator(Mediator mediator);
    String getName();
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
public class AddButton extends JButton implements Component {
    private Mediator mediator;

    public AddButton() {
        super("Add");
    }

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    protected void fireActionPerformed(ActionEvent actionEvent) {
        mediator.addNewNote(new Note());
    }

    @Override
    public String getName() {
        return "AddButton";
    }
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
public class DeleteButton extends JButton  implements Component {
    private Mediator mediator;

    public DeleteButton() {
        super("Del");
    }

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    protected void fireActionPerformed(ActionEvent actionEvent) {
        mediator.deleteNote();
    }

    @Override
    public String getName() {
        return "DelButton";
    }
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
public class Filter extends JTextField implements Component {
    private Mediator mediator;
    private ListModel listModel;

    public Filter() {}

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    protected void processComponentKeyEvent(KeyEvent keyEvent) {
        String start = getText();
        searchElements(start);
    }

    public void setList(ListModel listModel) {
        this.listModel = listModel;
    }

    private void searchElements(String s) {
        if (listModel == null) {
            return;
        }

        if (s.equals("")) {
            mediator.setElementsList(listModel);
            return;
        }

        ArrayList<Note> notes = new ArrayList<>();
        for (int i = 0; i < listModel.getSize(); i++) {
            notes.add((Note) listModel.getElementAt(i));
        }
        DefaultListModel<Note> listModel = new DefaultListModel<>();
        for (Note note : notes) {
            if (note.getName().contains(s)) {
                listModel.addElement(note);
            }
        }
        mediator.setElementsList(listModel);
    }

    @Override
    public String getName() {
        return "Filter";
    }
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
@SuppressWarnings("unchecked")
public class List extends JList implements Component {
    private Mediator mediator;
    private final DefaultListModel LIST_MODEL;

    public List(DefaultListModel listModel) {
        super(listModel);
        this.LIST_MODEL = listModel;
        setModel(listModel);
        this.setLayoutOrientation(JList.VERTICAL);
        Thread thread = new Thread(new Hide(this));
        thread.start();
    }

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void addElement(Note note) {
        LIST_MODEL.addElement(note);
        int index = LIST_MODEL.size() - 1;
        setSelectedIndex(index);
        ensureIndexIsVisible(index);
        mediator.sendToFilter(LIST_MODEL);
    }

    public void deleteElement() {
        int index = this.getSelectedIndex();
        try {
            LIST_MODEL.remove(index);
            mediator.sendToFilter(LIST_MODEL);
        } catch (ArrayIndexOutOfBoundsException ignored) {}
    }

    public Note getCurrentElement() {
        return (Note)getSelectedValue();
    }

    @Override
    public String getName() {
        return "List";
    }

    private class Hide implements Runnable {
        private List list;

        Hide(List list) {
            this.list = list;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                if (list.isSelectionEmpty()) {
                    mediator.hideElements(true);
                } else {
                    mediator.hideElements(false);
                }
            }
        }
    }
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
public class SaveButton extends JButton implements Component {
    private Mediator mediator;

    public SaveButton() {
        super("Save");
    }

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    protected void fireActionPerformed(ActionEvent actionEvent) {
        mediator.saveChanges();
    }

    @Override
    public String getName() {
        return "SaveButton";
    }
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
public class TextBox extends JTextArea implements Component {
    private Mediator mediator;

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    protected void processComponentKeyEvent(KeyEvent keyEvent) {
        mediator.markNote();
    }

    @Override
    public String getName() {
        return "TextBox";
    }
}
/**
 * Concrete components don't talk with each other. They have only one
 * communication channel–sending requests to the mediator.
 */
public class Title extends JTextField implements Component {
    private Mediator mediator;

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    protected void processComponentKeyEvent(KeyEvent keyEvent) {
        mediator.markNote();
    }

    @Override
    public String getName() {
        return "Title";
    }
}

Define a common mediator interface

/**
 * Common mediator interface.
 */
public interface Mediator {
    void addNewNote(Note note);
    void deleteNote();
    void getInfoFromList(Note note);
    void saveChanges();
    void markNote();
    void clear();
    void sendToFilter(ListModel listModel);
    void setElementsList(ListModel list);
    void registerComponent(Component component);
    void hideElements(boolean flag);
    void createGUI();
}

Specific intermediary

/**
 * Concrete mediator. All chaotic communications between concrete components
 * have been extracted to the mediator. Now components only talk with the
 * mediator, which knows who has to handle a request.
 */
public class Editor implements Mediator {
    private Title title;
    private TextBox textBox;
    private AddButton add;
    private DeleteButton del;
    private SaveButton save;
    private List list;
    private Filter filter;

    private JLabel titleLabel = new JLabel("Title:");
    private JLabel textLabel = new JLabel("Text:");
    private JLabel label = new JLabel("Add or select existing note to proceed...");
  
    /**
     * Here the registration of components by the mediator.
     */
    @Override
    public void registerComponent(Component component) {
        component.setMediator(this);
        switch (component.getName()) {
            case "AddButton":
                add = (AddButton)component;
                break;
            case "DelButton":
                del = (DeleteButton)component;
                break;
            case "Filter":
                filter = (Filter)component;
                break;
            case "List":
                list = (List)component;
                this.list.addListSelectionListener(listSelectionEvent -> {
                    Note note = (Note)list.getSelectedValue();
                    if (note != null) {
                        getInfoFromList(note);
                    } else {
                        clear();
                    }
                });
                break;
            case "SaveButton":
                save = (SaveButton)component;
                break;
            case "TextBox":
                textBox = (TextBox)component;
                break;
            case "Title":
                title = (Title)component;
                break;
        }
    }

    /**
     * Various methods to handle requests from particular components.
     */
    @Override
    public void addNewNote(Note note) {
        title.setText("");
        textBox.setText("");
        list.addElement(note);
    }

    @Override
    public void deleteNote() {
        list.deleteElement();
    }

    @Override
    public void getInfoFromList(Note note) {
        title.setText(note.getName().replace('*', ' '));
        textBox.setText(note.getText());
    }

    @Override
    public void saveChanges() {
        try {
            Note note = (Note) list.getSelectedValue();
            note.setName(title.getText());
            note.setText(textBox.getText());
            list.repaint();
        } catch (NullPointerException ignored) {}
    }

    @Override
    public void markNote() {
        try {
            Note note = list.getCurrentElement();
            String name = note.getName();
            if (!name.endsWith("*")) {
                note.setName(note.getName() + "*");
            }
            list.repaint();
        } catch (NullPointerException ignored) {}
    }

    @Override
    public void clear() {
        title.setText("");
        textBox.setText("");
    }

    @Override
    public void sendToFilter(ListModel listModel) {
        filter.setList(listModel);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void setElementsList(ListModel list) {
        this.list.setModel(list);
        this.list.repaint();
    }

    @Override
    public void hideElements(boolean flag) {
        titleLabel.setVisible(!flag);
        textLabel.setVisible(!flag);
        title.setVisible(!flag);
        textBox.setVisible(!flag);
        save.setVisible(!flag);
        label.setVisible(flag);
    }

    @Override
    public void createGUI() {
        JFrame notes = new JFrame("Notes");
        notes.setSize(960, 600);
        notes.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JPanel left = new JPanel();
        left.setBorder(new LineBorder(Color.BLACK));
        left.setSize(320, 600);
        left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
        JPanel filterPanel = new JPanel();
        filterPanel.add(new JLabel("Filter:"));
        filter.setColumns(20);
        filterPanel.add(filter);
        filterPanel.setPreferredSize(new Dimension(280, 40));
        JPanel listPanel = new JPanel();
        list.setFixedCellWidth(260);
        listPanel.setSize(320, 470);
        JScrollPane scrollPane = new JScrollPane(list);
        scrollPane.setPreferredSize(new Dimension(275, 410));
        listPanel.add(scrollPane);
        JPanel buttonPanel = new JPanel();
        add.setPreferredSize(new Dimension(85, 25));
        buttonPanel.add(add);
        del.setPreferredSize(new Dimension(85, 25));
        buttonPanel.add(del);
        buttonPanel.setLayout(new FlowLayout());
        left.add(filterPanel);
        left.add(listPanel);
        left.add(buttonPanel);
        JPanel right = new JPanel();
        right.setLayout(null);
        right.setSize(640, 600);
        right.setLocation(320, 0);
        right.setBorder(new LineBorder(Color.BLACK));
        titleLabel.setBounds(20, 4, 50, 20);
        title.setBounds(60, 5, 555, 20);
        textLabel.setBounds(20, 4, 50, 130);
        textBox.setBorder(new LineBorder(Color.DARK_GRAY));
        textBox.setBounds(20, 80, 595, 410);
        save.setBounds(270, 535, 80, 25);
        label.setFont(new Font("Verdana", Font.PLAIN, 22));
        label.setBounds(100, 240, 500, 100);
        right.add(label);
        right.add(titleLabel);
        right.add(title);
        right.add(textLabel);
        right.add(textBox);
        right.add(save);
        notes.setLayout(null);
        notes.getContentPane().add(left);
        notes.getContentPane().add(right);
        notes.setResizable(false);
        notes.setLocationRelativeTo(null);
        notes.setVisible(true);
    }
}

Notes

/**
 * Note class.
 */
public class Note {
    private String name;
    private String text;

    public Note() {
        name = "New note";
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getName() {
        return name;
    }

    public String getText() {
        return text;
    }

    @Override
    public String toString() {
        return name;
    }
}

setup code

/**
 * Demo class. Everything comes together here.
 */
public class Demo {
    public static void main(String[] args) {
        Mediator mediator = new Editor();

        mediator.registerComponent(new Title());
        mediator.registerComponent(new TextBox());
        mediator.registerComponent(new AddButton());
        mediator.registerComponent(new DeleteButton());
        mediator.registerComponent(new SaveButton());
        mediator.registerComponent(new List(new DefaultListModel()));
        mediator.registerComponent(new Filter());

        mediator.createGUI();
    }
}

Application scenario of pattern

1. The mediator pattern can be used when some objects are so tightly coupled with other objects that it is difficult to modify them.

This pattern allows you to extract all the relationships between objects into a single class, so that the modification of a specific component is independent of other components.

2. When a component is too dependent on other components to be reused in different applications, the mediator mode can be used.

After applying the mediator pattern, each component is no longer aware of other components. Although these components cannot communicate directly, they can communicate indirectly through mediator objects. If you want to reuse a component in different applications, you need to provide it with a new mediator class.

3. If you are forced to create a large number of component subclasses in order to reuse some basic behaviors in different scenarios, you can use the mediator pattern.

Since all the relationships between components are included in the mediator, you can easily create a mediator class to define a new way of component cooperation without modifying the component.

Example of JDK mediator pattern

  • java.util.Timer (all schedule s) ­ XXX (method)
  • java.util.concurrent.Executor#execute()
  • java.util.concurrent.ExecutorService ( invoke ­ XXX() and submit ­ (method)
  • java.util.concurrent.ScheduledExecutorService (all schedule s) ­ XXX (method)
  • java.lang.reflect.Method#invoke()

Relationship with other modes

  • Responsibility chain mode, command mode, mediator mode and observer mode are used to handle different connection modes between request sender and receiver:

    • The responsibility chain dynamically passes the request to a series of potential recipients in sequence until one of the recipients processes the request.
    • The command establishes a one-way connection between the sender and the requester.
    • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
    • Observers allow recipients to dynamically subscribe to or cancel receiving requests.
  • Facade patterns and mediators have similar responsibilities: they both try to organize cooperation in a large number of closely coupled classes.

    • The facade defines a simple interface for all objects in the subsystem, but it does not provide any new functionality. The appearance subsystem itself will not be aware of its existence. Objects in the subsystem can communicate directly.
    • Mediators centralize the communication behavior of components in the system. Each component only knows the mediator object and cannot communicate with each other directly.
  • The difference between a mediator and an observer is often hard to remember. In most cases, you can use either mode, and sometimes both. Let's see how to do this.

    The main goal of the mediator is to eliminate the interdependence between a series of system components. These components will depend on the same mediator object. The goal of the observer is to establish a dynamic one-way connection between objects, so that some objects can play a role as attachments to other objects.

    There is a popular mediator pattern whose implementation depends on the observer. The mediator object acts as a publisher, while other components act as subscribers and can subscribe to or unsubscribe from the mediator's events. When the mediator is implemented in this way, it may look very similar to the observer.

    When you are confused, remember that there are other ways to implement the mediator. For example, you can permanently link all components to the same mediator object. This implementation is different from the observer, but it is still a mediator model.

    Suppose there is a program, all its components become publishers, and they can establish dynamic connections with each other. In this way, there is no centralized mediator object in the program, but only some distributed observers.

Memo mode

intention

Allows you to save and restore an object's previous state without exposing its implementation details.

problem

Suppose you are developing a text editor application. In addition to the simple text editing function, the editor also has the functions of setting text format and inserting embedded pictures.

Later, you decide to allow the user to undo any action imposed on the text. This feature has become very common in the past few years, so users expect it to be available in any program. You choose to implement this function in a direct way: the program will record all object states and save them before performing any operation. When the user needs to undo an operation later, the program will take the latest snapshot from the history and then use it to restore the state of all objects.

The program saves the state snapshot of all objects before executing the operation, and the objects can be restored to their previous state later through the snapshot.

Let's think about these state snapshots. First of all, how to generate a snapshot? Most likely, you will need to traverse all member variables of the object and copy and save their values. But you can only use this method if the object has no strict access restrictions on its content. Unfortunately, most objects use private member variables to store important data, so that others can't easily view the content.

Now let's ignore this problem for the time being and assume that objects are like hippies: they like open relationships and disclose all their states. Although this method can solve the current problem and allow you to generate a snapshot of the state of objects at any time, there are still some serious problems with this method. You may add or remove some member variables in the future. This sounds simple, but changes need to be made to the class responsible for copying the state of the affected object.

How do I copy the private state of an object?

There are more questions. Let's consider the actual "snapshot" of the Editor state. What data does it need to contain? At least the actual text, cursor coordinates and current scroll bar position must be included. You need to collect this data and put it into a specific container to generate a snapshot.

You are likely to store a large number of container objects in the history list. In this way, there is a high probability that the container will eventually become an object of the same class. There are few methods in this class, but there are many member variables that correspond to the editor state one by one. In order for other objects to save or read the snapshot, you probably need to set the member variable of the snapshot to public. Whether or not these states are private, they expose all editor states. Other classes depend on every small change to the snapshot class, unless these changes exist only in private member variables or methods and do not affect the external class.

We seem to have entered a dead end: either we will expose all the internal details of the class and make it too fragile; Or access to its state will be restricted and the snapshot cannot be generated. So, are there any other ways to implement the "undo" function?

Solution

All the problems we have just encountered are caused by the "Breakage" of the package. Some objects try to work beyond their responsibilities. Because some behaviors need to get data, they invade the private space of other objects instead of letting them do the actual work.

Memo mode delegates the creation of state snapshots to the actual state owner Originator object. In this way, other objects no longer need to copy the editor state from the "outside". The editor class has full access to its state, so it can generate its own Snapshot.

The schema recommends storing a copy of the object's state in a special object called a memo. No object except the object that created the memo can access the contents of the memo. Other objects must use the restricted interface to interact with the memo. They can obtain the metadata of the snapshot (creation time, operation name, etc.), but cannot obtain the state of the original object in the snapshot.

The originator has full access to the memo, and the person in charge can only access the metadata.

This restriction policy allows you to keep memos in objects commonly known as Caretakers. Since the person in charge interacts with the memo only through the restricted interface, he cannot modify the status stored in the memo. At the same time, the initiator has access to all members of the memo, so it can restore its previous state at any time.

In the text editor example, we can create a separate History class as the owner. The memo stack stored in the person in charge grows every time the editor performs an operation. You can even render the stack in the application's UI to display the user's previous operation History.

When the user triggers the undo operation, the history class will retrieve the latest memo from the stack and pass it to the editor to request rollback. Since the editor has full access to the memo, it can replace its state with the value obtained from the memo.

Pattern structure

Implementation based on nested classes

The classic implementation of this pattern relies on nested classes supported by many popular programming languages, such as C + +, c# and Java.

The state of the original class can be restored by the original class when the snapshot is generated.

A memo is a value object of the initiator state snapshot. The usual approach is to make the memo immutable and pass the data at one time through the constructor.

The Caretaker only knows "when" and "why" to capture the state of the initiator and when to restore the state.

The person in charge records the historical status of the initiator by saving the memo stack. When the originator needs to trace back the historical state, the person in charge will get the top memo from the stack and pass it to the recovery method of the originator.

In this implementation method, the memo class will be nested in the originator. In this way, the initiator can access the member variables and methods of the memo, even if these methods are declared private. On the other hand, the person in charge has very limited access to the member variables and methods of the memo: they can only save the memo in the stack, not modify its state.

Encapsulate a more rigorous implementation

If you don't want other classes to have any chance to access the state of the initiator through memos, there is another implementation available.

This implementation allows the existence of many different types of initiators and memos. Each initiator interacts with its corresponding memo class. Neither the initiator nor the memo will expose its state to other classes.

At this time, the person in charge is explicitly prohibited from modifying the status stored in the memo. However, the responsible human will be independent of the primary generator, because the recovery method is defined in the memo class.

Each memo will be connected to the originator that created it. The originator passes itself and its state to the constructor of the memo. Due to the close relationship between these classes, as long as the originator defines an appropriate setter, the memo can restore its state.

General code implementation

public class MementoPatternDemo {
   
   public static void main(String[] args) {
      Originator originator = new Originator();
        // Intermediate data ready
        originator.prepare();
        // Save the intermediate data in the memo
        Memento memento = originator.createMemento();
        // Save the memo to the memo manager
        Caretaker caretaker = new Caretaker();
        caretaker.saveMemento(memento);
        // Method A is executed based on the intermediate data, but the intermediate data has changed at this time
        originator.executeA();
        // Get memo from memo Manager
        memento = caretaker.retrieveMemento();
        // Reset the intermediate data saved in the memo to the originator to restore the intermediate data to the state of the previous memo
        originator.setMemento(memento);
        // Then perform method B again
        originator.executeB();
   }
   
   public interface Memento {
      
   }
   
   public static class Originator {
       
       private String state;
       
       public void prepare() {
           this.state = "Intermediate data";
       }
       
       public void executeA() {
           System.out.println("Based on intermediate data[" + state +"]Yes A Logic of method");
           // The intermediate data represented by state has been modified
           state += ",A Result data of method";
       }
       
       public void executeB() {
           System.out.println("Based on intermediate data[" + state +"]Yes B Logic of method");
           state += ",B Result data of method";
       }
       
       public Memento createMemento() {
           return new MementoImpl(state);
       }
       
       public void setMemento(Memento memento) {
           MementoImpl mementoImpl = (MementoImpl)memento;
           this.state = mementoImpl.getState();
       }
       
       private static class MementoImpl implements Memento {
           
           private String state;
           
           public MementoImpl(String state) {
               this.state = state;
           }
           
           public String getState() {
               return state;
           }
           
       }
       
   }
   
   public static class Caretaker {
       
       private Memento memento;
       
       public void saveMemento(Memento memento) {
           this.memento = memento;
       }
       
       public Memento retrieveMemento() {
           return this.memento;
       }
       
   }
   
}

Status update

public class MementoPattern {
    public static void main(String[] args) {
        Originator or = new Originator();
        Caretaker cr = new Caretaker();
        or.setState("S0");
        System.out.println("Initial state:" + or.getState());
        cr.setMemento(or.createMemento()); //Save status      
        or.setState("S1");
        System.out.println("New status:" + or.getState());
        or.restoreMemento(cr.getMemento()); //Restore state
        System.out.println("Restore state:" + or.getState());
    }
}

//memorandum
class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}

//Sponsor
class Originator {
    private String state;

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public Memento createMemento() {
        return new Memento(state);
    }

    public void restoreMemento(Memento m) {
        this.setState(m.getState());
    }
}

//controller
class Caretaker {
    private Memento memento;

    public void setMemento(Memento m) {
        memento = m;
    }

    public Memento getMemento() {
        return memento;
    }
}

Output result:

Initial state:S0
 New status:S1
 Restore state:S0

Application scenario of pattern

1. When you need to create a snapshot of the state of an object to restore its previous state, you can use memo mode.

Memo mode allows you to copy all States in an object (including private member variables) and save them independently of the object. Although most people remember this pattern because of the use case of "undo", it is also essential in the process of dealing with transactions, such as rolling back an operation when an error occurs.

2. This pattern can be used when direct access to an object's member variable, getter, or setter will lead to a breakthrough in encapsulation.

Memos let objects take responsibility for creating snapshots of their state. No other object can read the snapshot, which effectively ensures the security of the data.

Example of memo mode in JDK

  • All Java io. Serializable implementations can simulate memos.
  • All javax faces. component. Implementation of stateholder.

Relationship with other modes

  • You can use both command mode and memo mode to "undo". In this case, the command is used to perform various operations on the target object, and the memo is used to save the state of the object before a command is executed.

  • You can use both memo and iterator mode to get the current iterator state and roll back when needed.

  • Sometimes the prototype pattern can be used as a simplified version of the memo, provided that the state of the object you need to store in the history is relatively simple, there is no need to link other external resources, or the link can be easily rebuilt.

Observer mode

Allows you to define a subscription mechanism that can notify multiple other objects that "observe" the object when an object event occurs.

problem

Suppose you have two types of objects: customers and stores. Customers are very interested in a particular brand of product (such as the latest model of iPhone), which will soon be sold in stores.

Customers can come to the store every day to see if the products have arrived. But if the goods have not arrived yet, the vast majority of customers who come to the store will return empty handed.

Go to the store and send spam

On the other hand, every time a new product arrives, the store can send email to all customers (which may be regarded as spam). In this way, some customers do not need to go to the store repeatedly, but it may annoy other customers who are not interested in the new product.

We seem to have encountered a contradiction: either let customers waste time checking whether products arrive, or let stores waste resources to inform customers who don't need them.

Solution

An object with some noteworthy state is usually called a target. Because it wants to notify other objects of its state changes, we also call it a publisher. All other objects that want to follow changes in the publisher's state are called subscribers.

Observer mode suggests that you add a subscription mechanism to the publisher class so that each object can subscribe or unsubscribe to the publisher event flow. Don't be scared! It's not as complicated as it sounds. In fact, the mechanism includes 1) a list member variable for storing the subscriber object reference; 2) Several public methods for adding or removing subscribers from the list.

The subscription mechanism allows objects to subscribe to event notifications.

Now, whenever an important publisher event occurs, it traverses the subscriber and calls a specific notification method of its object.

In practical application, there may be more than a dozen different subscriber classes tracking the events of the same publisher class. You don't want the publisher to be coupled with all these classes. In addition, if others will use publisher classes, you may even know nothing about some of them.

Therefore, all subscribers must implement the same interface, and publishers only interact with subscribers through this interface. The notification method and its parameters must be declared in the interface, so that the publisher can pass some context data when issuing the notification.

The publisher calls a specific notification method in the subscriber object to notify the subscriber.

If you have multiple different types of publishers in your application and want subscribers to be compatible with all publishers, you can even further make all subscribers follow the same interface. This interface only needs to describe a few subscription methods. In this way, the subscriber can observe the state of the publisher through the interface without coupling with the specific publisher class.

Real world analogy

Magazine and newspaper subscriptions.

If you subscribe to a magazine or newspaper, you don't need to go to the newsstand to check for new publications. The publisher (i.e. "publisher" in the application) will send the latest issue directly to your email after the publication (or even in advance).

The publishing house is responsible for maintaining the list of subscribers and understanding which publications subscribers are interested in. Subscribers can drop out of the list at any time when they want the publisher to stop sending new issues. Observer mode structure

Pattern structure

Publishers send interesting events to other objects. Events occur after the Publisher's own state changes or performs a specific behavior. The Publisher contains a subscription framework that allows new subscribers to join and current subscribers to leave the list.

When a new event occurs, the sender will traverse the subscription list and call the notification method of each subscriber object. The method is declared in the subscriber interface.

The Subscriber interface declares a notification interface. In most cases, the interface contains only one update method. This method can have multiple parameters, so that the publisher can pass the details of the event when updating.

Concrete Subscribers can perform some actions to respond to the publisher's notification. All concrete subscriber classes implement the same interface, so publishers do not need to be coupled with concrete classes.

Subscribers usually need some context information to handle updates correctly. Therefore, publishers usually pass some context data as parameters of notification methods. Publishers can also pass themselves as parameters to enable subscribers to directly obtain the required data.

The Client creates publisher and subscriber objects respectively, and then registers publisher updates for subscribers.

General code implementation

public class ObserverPatternDemo {
   
   public static void main(String[] args) {
      Subject subject = new Subject(0); 
      
      Observer observer = new ConcreteObserver();
      subject.addObserver(observer); 
      
      subject.setState(1);
      subject.setState(2);  
   }
   
   public static class Subject extends Observable {
      
      private Integer state;
      
      public Subject(Integer state) {
         this.state = state;
      }
      
      public Integer getState() {
         return state;
      }
      public void setState(Integer state) {
         // Here the state changes
         this.state = state;
         // Notify some associated observers that my state has changed
         this.setChanged();
         this.notifyObservers();
      }
      
   }
   
   public static class ConcreteObserver implements Observer {

      @Override
      public void update(Observable o, Object arg) {
         Subject subject = (Subject) o;
         Integer state = subject.getState();
         System.out.println("The state of the target object changes to:" + state);  
      }
      
   }
   
}

event subscriptions

Usage example: observer mode is common in Java code, especially in GUI components. It provides a way to react to its events without coupling with the classes to which other objects belong.

In this case, the observer pattern establishes an indirect partnership between the objects of the text Editor. Whenever an Editor object changes, it notifies its subscribers. Mail notification listener (Email) ­ Notification ­ Listener and Log on listener ­ Open ­ Listeners) will respond to these notifications by performing their basic behavior.

The subscriber class is not coupled with the editor class, and can be reused in other applications when needed. The editor class depends only on the abstract subscriber interface. This allows you to add new subscriber types without changing the editor code.

Basic publisher

public class EventManager {
    Map<String, List<EventListener>> listeners = new HashMap<>();

    public EventManager(String... operations) {
        for (String operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    public void subscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    public void notify(String eventType, File file) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.update(eventType, file);
        }
    }
}

Specific publisher, tracked by other objects

public class Editor {
    public EventManager events;
    private File file;

    public Editor() {
        this.events = new EventManager("open", "save");
    }

    public void openFile(String filePath) {
        this.file = new File(filePath);
        events.notify("open", file);
    }

    public void saveFile() throws Exception {
        if (this.file != null) {
            events.notify("save", file);
        } else {
            throw new Exception("Please open a file first.");
        }
    }
}

Send email after receiving notification

public class EmailNotificationListener implements EventListener {
    private String email;

    public EmailNotificationListener(String email) {
        this.email = email;
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}

After receiving the notification, record a message in the log

public class LogOpenListener implements EventListener {
    private File log;

    public LogOpenListener(String fileName) {
        this.log = new File(fileName);
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}

setup code

public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
        editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));

        try {
            editor.openFile("test.txt");
            editor.saveFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Application scenario of observer mode

1. Observer mode can be used when the change of the state of an object needs to change other objects, or the actual object is unknown or dynamically changed in advance.

When you use graphical user interface classes, you usually encounter a problem. For example, you create a custom button class and allow the client to inject custom code into the button, which will trigger when the user presses the button.

The observer pattern allows any object that implements the subscriber interface to subscribe to event notifications for the publisher object. You can add a subscription mechanism in the button to allow the client to inject custom code through the custom subscription class.

2. This mode can be used when some objects in the application must observe other objects. However, it can only be used for a limited time or under specific circumstances.

The subscription list is dynamic, so subscribers can join or leave the list at any time.

Example of observer mode in JDK

  • java.util.Observer/ java.util.Observable
  • java. util. All implementations of EventListener
  • javax.servlet.http.HttpSessionBindingListener
  • javax.servlet.http.HttpSessionAttributeListener
  • javax.faces.event.PhaseListener

Relationship with other modes

  • Responsibility chain mode, command mode, mediator mode and observer mode are used to handle different connection modes between request sender and receiver:

    • The responsibility chain dynamically passes the request to a series of potential recipients in sequence until one of the recipients processes the request.
    • The command establishes a one-way connection between the sender and the requester.
    • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
    • Observers allow recipients to dynamically subscribe to or cancel receiving requests.
  • The difference between a mediator and an observer is often hard to remember. In most cases, you can use either mode, and sometimes both. Let's see how to do this.

    The main goal of the mediator is to eliminate the interdependence between a series of system components. These components will depend on the same mediator object. The goal of the observer is to establish a dynamic one-way connection between objects, so that some objects can play a role as attachments to other objects.

    There is a popular mediator pattern whose implementation depends on the observer. The mediator object acts as a publisher, while other components act as subscribers and can subscribe to or unsubscribe from the mediator's events. When the mediator is implemented in this way, it may look very similar to the observer.

    When you are confused, remember that there are other ways to implement the mediator. For example, you can permanently link all components to the same mediator object. This implementation is different from the observer, but it is still a mediator model.

    Suppose there is a program, all its components become publishers, and they can establish dynamic connections with each other. In this way, there is no centralized mediator object in the program, but only some distributed observers.

State mode

intention

It allows you to change the behavior of an object when its internal state changes, making it look like it has changed its own class.

problem

State mode is closely related to the concept of finite state machine.

Finite state machine.

The main idea is that the program can only be in several limited states at any time. In any particular state, the behavior of the program is different, and it can switch from one state to another instantly. However, depending on the current state, the program may switch to another state or keep the current state unchanged. These limited and predefined state switching rules are called transitions.

You can also apply this method to objects. Suppose you have a Document class. A Document may be in one of three states: Draft, under review, Moderation, and Published. The publish method of documents behaves slightly differently in different states:

  • When in the draft state, it moves the document to the review state.
  • When in the review status, if the current user is an administrator, it will publish the document publicly.
  • When it is in the published state, it does nothing.

All States and transitions of document objects.

The state machine is usually implemented by many conditional operators (if or switch), and the corresponding behavior can be selected according to the current state of the object. Status is usually just a set of member variable values in an object. Even if you've never heard of a finite state machine before, you've probably implemented state mode.

When we gradually add more states and state dependent behaviors to document classes, the state machine based on conditional statements will expose its biggest weakness. In order to select the method to complete the corresponding behavior according to the current state, most methods will contain complex conditional statements. Modifying its transformation logic may involve modifying the state condition statements in all methods, resulting in very difficult code maintenance.

This problem will become more and more serious as the project progresses. It is difficult to predict all possible states and transitions in the design phase. Over time, a concise state machine that initially contained only finite conditional statements may become a bloated mess.

Solution

State mode suggests creating a new class for all possible states of an object, and then extracting the corresponding behaviors of all States into these classes.

The original object is called context. It will not implement all behaviors by itself. Instead, it will save a reference to the state object representing the current state and delegate all state related work to the object.

The document delegates work to a status object.

To convert the context to another state, replace the currently active state object with another object representing the new state. This approach is based on the premise that all state classes must follow the same interface, and the context must interact with these objects only through the interface.

This structure may look similar to the policy pattern, but there is one key difference - in the state pattern, a specific state knows the existence of all other states and can trigger the transition from one state to another; Strategy is almost completely unaware of the existence of other strategies.

Real world analogy

The buttons and switches of the smartphone will complete different behaviors according to the current state of the device:

  • When the phone is unlocked, press the key to perform various functions.
  • When the phone is locked, pressing any key will unlock the screen.
  • When the phone is low, pressing any key will display the charging page.

Pattern structure

Context holds a reference to a specific state object and delegates all work related to the state to it. The context interacts with the state object through the state interface and provides a setter to pass the new state object.

The State interface declares State specific methods. These methods should be understood by all other specific states, because you don't want the methods owned by some states to never be called.

Concrete States implement their own state specific methods. To avoid similar code in multiple states, you can provide an intermediate abstract class that encapsulates some common behavior.

A state object stores a back reference to a context object. The state can obtain the required information from the context through this reference, and can trigger the state transition.

Both the context and the specific state can set the next state of the context, and the actual state transition can be completed by replacing the state object connected to the context.

General code implementation

public class StatePatternDemo {
   
   public static void main(String[] args) {
      Context context = new Context(new NewState());
      context.execute(1); 
      context.execute(2); 
      context.execute(3);
   }
   
   public interface State {
      void execute();
   }
   
   public static class NewState implements State {
      @Override
        public void execute() {
         System.out.println("Execute the logic of creating sales issue doc");  
      }
   }
   
   public static class ApprovingState implements State {
      @Override
        public void execute() {
         System.out.println("Execute the logic of sales issue doc in pending approval status");  
      }
   }
   
   public static class ApprovedState implements State {
      @Override
        public void execute() {
         System.out.println("Execute the logic of sales issue doc in approved status");  
      }
   }
   
   public static class FinishedState implements State {
      @Override
        public void execute() {
         System.out.println("Execute the logic of sales issue doc in completed status");  
      }
   }
   
   public static class Context {
      
      private State state;
      
      public Context(State state) {
         this.state = state;
         this.state.execute();
      }
      
      public void execute(int stateType) {
         if(stateType == 1) {
            this.state = new ApprovingState();
            this.state.execute();
         } else if(stateType == 2) {
            this.state = new ApprovedState();
            this.state.execute();
         } else if(stateType == 3) {
            this.state = new FinishedState();
            this.state.execute();
         }
      }
      
   }
   
}

Interface of media player

Usage example: in the Java language, state mode is usually used to convert large state machines based on switch statements into objects.

In this example, the state mode allows the media player to perform different control behaviors according to the current playback state. The player main class contains a reference to the state object, which will do most of the work of the player. Some behaviors may replace one state object with another, changing the way the player responds to user interaction.

General status interface

/**
 * Common interface for all states.
 */
public abstract class State {
    Player player;

    /**
     * Context passes itself through the state constructor. This may help a
     * state to fetch some useful context data if needed.
     */
    State(Player player) {
        this.player = player;
    }

    public abstract String onLock();
    public abstract String onPlay();
    public abstract String onNext();
    public abstract String onPrevious();
}
/**
 * Concrete states provide the special implementation for all interface methods.
 */
public class LockedState extends State {

    LockedState(Player player) {
        super(player);
        player.setPlaying(false);
    }

    @Override
    public String onLock() {
        if (player.isPlaying()) {
            player.changeState(new ReadyState(player));
            return "Stop playing";
        } else {
            return "Locked...";
        }
    }

    @Override
    public String onPlay() {
        player.changeState(new ReadyState(player));
        return "Ready";
    }

    @Override
    public String onNext() {
        return "Locked...";
    }

    @Override
    public String onPrevious() {
        return "Locked...";
    }
}
/**
 * They can also trigger state transitions in the context.
 */
public class ReadyState extends State {

    public ReadyState(Player player) {
        super(player);
    }

    @Override
    public String onLock() {
        player.changeState(new LockedState(player));
        return "Locked...";
    }

    @Override
    public String onPlay() {
        String action = player.startPlayback();
        player.changeState(new PlayingState(player));
        return action;
    }

    @Override
    public String onNext() {
        return "Locked...";
    }

    @Override
    public String onPrevious() {
        return "Locked...";
    }
}
public class PlayingState extends State {

    PlayingState(Player player) {
        super(player);
    }

    @Override
    public String onLock() {
        player.changeState(new LockedState(player));
        player.setCurrentTrackAfterStop();
        return "Stop playing";
    }

    @Override
    public String onPlay() {
        player.changeState(new ReadyState(player));
        return "Paused...";
    }

    @Override
    public String onNext() {
        return player.nextTrack();
    }

    @Override
    public String onPrevious() {
        return player.previousTrack();
    }
}

Main code of player

public class Player {
    private State state;
    private boolean playing = false;
    private List<String> playlist = new ArrayList<>();
    private int currentTrack = 0;

    public Player() {
        this.state = new ReadyState(this);
        setPlaying(true);
        for (int i = 1; i <= 12; i++) {
            playlist.add("Track " + i);
        }
    }

    public void changeState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public void setPlaying(boolean playing) {
        this.playing = playing;
    }

    public boolean isPlaying() {
        return playing;
    }

    public String startPlayback() {
        return "Playing " + playlist.get(currentTrack);
    }

    public String nextTrack() {
        currentTrack++;
        if (currentTrack > playlist.size() - 1) {
            currentTrack = 0;
        }
        return "Playing " + playlist.get(currentTrack);
    }

    public String previousTrack() {
        currentTrack--;
        if (currentTrack < 0) {
            currentTrack = playlist.size() - 1;
        }
        return "Playing " + playlist.get(currentTrack);
    }

    public void setCurrentTrackAfterStop() {
        this.currentTrack = 0;
    }
}

GUI of player

public class UI {
    private Player player;
    private static JTextField textField = new JTextField();

    public UI(Player player) {
        this.player = player;
    }

    public void init() {
        JFrame frame = new JFrame("Test player");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel context = new JPanel();
        context.setLayout(new BoxLayout(context, BoxLayout.Y_AXIS));
        frame.getContentPane().add(context);
        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
        context.add(textField);
        context.add(buttons);

        // Context delegates handling user's input to a state object. Naturally,
        // the outcome will depend on what state is currently active, since all
        // states can handle the input differently.
        JButton play = new JButton("Play");
        play.addActionListener(e -> textField.setText(player.getState().onPlay()));
        JButton stop = new JButton("Stop");
        stop.addActionListener(e -> textField.setText(player.getState().onLock()));
        JButton next = new JButton("Next");
        next.addActionListener(e -> textField.setText(player.getState().onNext()));
        JButton prev = new JButton("Prev");
        prev.addActionListener(e -> textField.setText(player.getState().onPrevious()));
        frame.setVisible(true);
        frame.setSize(300, 100);
        buttons.add(play);
        buttons.add(stop);
        buttons.add(next);
        buttons.add(prev);
    }
}

setup code

/**
 * Demo class. Everything comes together here.
 */
public class Demo {
    public static void main(String[] args) {
        Player player = new Player();
        UI ui = new UI(player);
        ui.init();
    }
}

Application scenario of pattern

1. If the object needs to perform different behaviors according to its current state, and the number of States is very large, and the code related to the state will change frequently, the state mode can be used.

Patterns suggest that you extract all state specific code into a separate set of classes. In this way, you can add new states or modify existing states independently of other states, thus reducing maintenance costs.

2. This mode can be used if a class needs to change its behavior according to the current value of member variables, so it needs to use a large number of conditional statements.

The state pattern extracts the branches of these conditional statements into the methods of the corresponding state class. At the same time, you can also clear the temporary member variables and helper method code related to a specific state in the main class.

3. State patterns can be used when there are many duplicate codes in similar states and condition based state machine transitions.

State patterns allow you to generate state class hierarchies that reduce duplication by extracting common code into abstract base classes.

Example of state mode in JDK

  • javax.faces.lifecycle.LifeCycle#execute() (by Faces) ­ Servlet control: the behavior depends on the phase (state) of the current JSF life cycle)

Relationship with other modes

  • The interfaces of bridge mode, state mode and policy mode (including adapter mode to some extent) are very similar. In fact, they are all based on a composite pattern -- delegating work to other objects, but each solves different problems. Patterns are not just recipes for organizing code in a specific way. You can also use them to discuss the problems solved by patterns with other developers.

  • The state can be considered as an extension of the policy. Both are based on a combination mechanism: they both change their behavior in different situations by delegating part of their work to "helper" objects. The policy makes these objects completely independent of each other, and they do not know the existence of other objects. However, the state mode does not limit the dependence between specific states, and allows them to change their states under different scenarios.

Strategy mode

intention

It allows you to define a series of algorithms and put each algorithm into a separate class, so that the objects of the algorithm can be replaced with each other.

problem

One day, you plan to create a tour guide program for tourists. The core function of the program is to provide beautiful maps to help users locate quickly in any city.

The new feature users expect is automatic route planning: they want to enter the address and see the fastest route to their destination on the map.

The first version of the program can only plan highway routes. People traveling by car are very satisfied with it. But obviously, not everyone will drive on vacation. Therefore, you added the function of planning pedestrian routes in the next update. Since then, you have added the function of planning public transport routes.

And this is just the beginning. Before long, you have to plan the route for the riders. After another period of time, you have to plan the route for visiting all the scenic spots in the city.

The guide code will become very bloated.

Although this application is very successful from a commercial point of view, its technical part gives you a headache: each time you add a new route planning algorithm, the volume of the main classes in the tour guide application will double. Finally, at some point, you feel you can't continue to maintain this pile of code.

Whether it is to fix simple defects or fine tune Street weights, any modification to an algorithm will affect the whole class, thus increasing the risk of introducing errors into the existing normal running code.

In addition, teamwork will become inefficient. If you recruit team members after the successful release of the application, they will complain that they spend too much time merging conflicts. In the process of implementing new functions, your team needs to modify the same huge class, so that the code they write may conflict with each other.

Solution

The policy pattern suggests finding out the classes responsible for completing specific tasks in many different ways, and then extracting the algorithms into a set of independent classes called policies.

The original class named context must contain a member variable to store references to each policy. The context does not perform tasks, but delegates work to connected policy objects.

The context is not responsible for selecting the algorithm that meets the needs of the task -- the client will pass the required policy to the context. In fact, the context does not know the policy very well. It will interact with all policies through the same general interface, and the interface only needs to expose a method to trigger the algorithm encapsulated in the selected policy.

Therefore, the context can be independent of the specific policy. In this way, you can add new algorithms or modify existing algorithms without modifying the context code or other strategies.

Route planning strategy.

In the application of tour guide, each route planning algorithm can be extracted to only one build ­ Route is generated in a separate class of route methods. This method takes the starting point and ending point as parameters and returns the set of Midway points of the route.

Even if the parameters passed to each path planning class are identical, the routes they create may be completely different. The main work of the main tour guide class is to render a series of Midway points on the map, and will not care about how to choose the algorithm. There is also a method for switching the current path planning policy in this class, so the client (such as the button in the user interface) can replace the currently selected path planning behavior with other policies.

Real world analogy

Various travel strategies to the airport

If you need to go to the airport. You can choose to take a bus, make an appointment for a taxi or ride a bike. These are your travel strategies. You can choose one of these strategies based on time or other factors.

Pattern structure

Context maintains a reference to a specific policy and communicates with the object only through the policy interface.

The Strategy interface is a common interface for all specific policies. It declares a method used by the context to execute the policy.

Concrete Strategies implement various variants of the algorithm used in the context.

When the context needs to run the algorithm, it will call the execution method on its connected policy object. The context is unclear about the type of policy involved and the execution mode of the algorithm.

The Client creates a specific policy object and passes it to the context. The context provides a setter so that the Client can replace the associated policy at run time.

General code implementation

public class StrategryPatternDemo {

   public static void main(String[] args) {
      int discountStyle = 1;
      
      DiscountCalculateStrategy strategy = DiscountCalculateStrategryFactory
            .getDiscountCalculateStrategy(discountStyle);
      
      Context context = new Context();
      context.setStrategy(strategy); 
      context.calculate();
   }
   
   public interface DiscountCalculateStrategy {
      void calculate();
   }
   
   public static class DiscountCalculateStrategyA implements DiscountCalculateStrategy {
      @Override
      public void calculate() {
         System.out.println("Implement the complex business logic of preferential pricing method 1"); 
      }
   }
   
   public static class DiscountCalculateStrategyB implements DiscountCalculateStrategy {
      @Override
      public void calculate() {
         System.out.println("Complex business logic for implementing preferential pricing method 2"); 
      }
   }
   
   public static class DiscountCalculateStrategyC implements DiscountCalculateStrategy {
      @Override
      public void calculate() {
         System.out.println("Complex business logic for implementing preferential pricing method 3"); 
      }
   }
   
   public static class DiscountCalculateStrategyDefault implements DiscountCalculateStrategy {
      @Override
      public void calculate() {
         System.out.println("Implement the complex business logic of the default preferential pricing method"); 
      }
   }
   
   public static class DiscountCalculateStrategryFactory {
      public static DiscountCalculateStrategy getDiscountCalculateStrategy(int discountStyle) {
         if(discountStyle == 1) {
            return new DiscountCalculateStrategyA();
         } else if(discountStyle == 2) {
            return new DiscountCalculateStrategyB();
         } else if(discountStyle == 3) { 
            return new DiscountCalculateStrategyC();
         } else {
            return new DiscountCalculateStrategyDefault();
         }
      }
      
   }
   
   public static class Context {
      
      private DiscountCalculateStrategy strategy;

      public DiscountCalculateStrategy getStrategy() {
         return strategy;
      }

      public void setStrategy(DiscountCalculateStrategy strategy) {
         this.strategy = strategy;
      }
      
      public void calculate() {
         strategy.calculate();
      }
      
   }
   
}

Payment methods in e-commerce applications

Usage example: Policy patterns are common in Java code. It is often used in various frameworks and can provide users with ways to change their behavior without extending classes.

Java 8 began to support the lambda method, which can be used as a simple way to replace the policy pattern.

In this example, the policy pattern is used to implement various payment methods in e-commerce applications. After selecting the goods they want to buy, customers need to choose a payment method: Paypal or credit card.

The specific strategy will not only complete the actual payment work, but also change the behavior of the payment form, and provide corresponding fields in the form to record the payment information.

General payment method interface

/**
 * Common interface for all strategies.
 */
public interface PayStrategy {
    boolean pay(int paymentAmount);
    void collectPaymentDetails();
}

Pay with PayPal

/**
 * Concrete strategy. Implements PayPal payment method.
 */
public class PayByPayPal implements PayStrategy {
    private static final Map<String, String> DATA_BASE = new HashMap<>();
    private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
    private String email;
    private String password;
    private boolean signedIn;

    static {
        DATA_BASE.put("amanda1985", "amanda@ya.com");
        DATA_BASE.put("qwerty", "john@amazon.eu");
    }

    /**
     * Collect customer's data.
     */
    @Override
    public void collectPaymentDetails() {
        try {
            while (!signedIn) {
                System.out.print("Enter the user's email: ");
                email = READER.readLine();
                System.out.print("Enter the password: ");
                password = READER.readLine();
                if (verify()) {
                    System.out.println("Data verification has been successful.");
                } else {
                    System.out.println("Wrong email or password!");
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private boolean verify() {
        setSignedIn(email.equals(DATA_BASE.get(password)));
        return signedIn;
    }

    /**
     * Save customer data for future shopping attempts.
     */
    @Override
    public boolean pay(int paymentAmount) {
        if (signedIn) {
            System.out.println("Paying " + paymentAmount + " using PayPal.");
            return true;
        } else {
            return false;
        }
    }

    private void setSignedIn(boolean signedIn) {
        this.signedIn = signedIn;
    }
}

Pay by credit card

/**
 * Concrete strategy. Implements credit card payment method.
 */
public class PayByCreditCard implements PayStrategy {
    private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
    private CreditCard card;

    /**
     * Collect credit card data.
     */
    @Override
    public void collectPaymentDetails() {
        try {
            System.out.print("Enter the card number: ");
            String number = READER.readLine();
            System.out.print("Enter the card expiration date 'mm/yy': ");
            String date = READER.readLine();
            System.out.print("Enter the CVV code: ");
            String cvv = READER.readLine();
            card = new CreditCard(number, date, cvv);

            // Validate credit card number...

        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * After card validation we can charge customer's credit card.
     */
    @Override
    public boolean pay(int paymentAmount) {
        if (cardIsPresent()) {
            System.out.println("Paying " + paymentAmount + " using Credit Card.");
            card.setAmount(card.getAmount() - paymentAmount);
            return true;
        } else {
            return false;
        }
    }

    private boolean cardIsPresent() {
        return card != null;
    }
}

Credit card

/**
 * Dummy credit card class.
 */
public class CreditCard {
    private int amount;
    private String number;
    private String date;
    private String cvv;

    CreditCard(String number, String date, String cvv) {
        this.amount = 100_000;
        this.number = number;
        this.date = date;
        this.cvv = cvv;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public int getAmount() {
        return amount;
    }
}

Order class

/**
 * Order class. Doesn't know the concrete payment method (strategy) user has
 * picked. It uses common strategy interface to delegate collecting payment data
 * to strategy object. It can be used to save order to database.
 */
public class Order {
    private int totalCost = 0;
    private boolean isClosed = false;

    public void processOrder(PayStrategy strategy) {
        strategy.collectPaymentDetails();
        // Here we could collect and store payment data from the strategy.
    }

    public void setTotalCost(int cost) {
        this.totalCost += cost;
    }

    public int getTotalCost() {
        return totalCost;
    }

    public boolean isClosed() {
        return isClosed;
    }

    public void setClosed() {
        isClosed = true;
    }
}

Client code

/**
 * World first console e-commerce application.
 */
public class Demo {
    private static Map<Integer, Integer> priceOnProducts = new HashMap<>();
    private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    private static Order order = new Order();
    private static PayStrategy strategy;

    static {
        priceOnProducts.put(1, 2200);
        priceOnProducts.put(2, 1850);
        priceOnProducts.put(3, 1100);
        priceOnProducts.put(4, 890);
    }

    public static void main(String[] args) throws IOException {
        while (!order.isClosed()) {
            int cost;

            String continueChoice;
            do {
                System.out.print("Please, select a product:" + "\n" +
                        "1 - Mother board" + "\n" +
                        "2 - CPU" + "\n" +
                        "3 - HDD" + "\n" +
                        "4 - Memory" + "\n");
                int choice = Integer.parseInt(reader.readLine());
                cost = priceOnProducts.get(choice);
                System.out.print("Count: ");
                int count = Integer.parseInt(reader.readLine());
                order.setTotalCost(cost * count);
                System.out.print("Do you wish to continue selecting products? Y/N: ");
                continueChoice = reader.readLine();
            } while (continueChoice.equalsIgnoreCase("Y"));

            if (strategy == null) {
                System.out.println("Please, select a payment method:" + "\n" +
                        "1 - PalPay" + "\n" +
                        "2 - Credit Card");
                String paymentMethod = reader.readLine();

                // Client creates different strategies based on input from
                // user, application configuration, etc.
                                                // пользовательских данных, конфигурации и прочих параметров.
                if (paymentMethod.equals("1")) {
                    strategy = new PayByPayPal();
                } else {
                    strategy = new PayByCreditCard();
                }
            }

            // Order object delegates gathering payment data to strategy
            // object, since only strategies know what data they need to
            // process a payment.
                                    // т.к. только стратегии знают какие данные им нужны для приёма
            // оплаты.
            order.processOrder(strategy);

            System.out.print("Pay " + order.getTotalCost() + " units or Continue shopping? P/C: ");
            String proceed = reader.readLine();
            if (proceed.equalsIgnoreCase("P")) {
                // Finally, strategy handles the payment.
                                                if (strategy.pay(order.getTotalCost())) {
                    System.out.println("Payment has been successful.");
                } else {
                    System.out.println("FAIL! Please, check your data.");
                }
                order.setClosed();
            }
        }
    }
}

Application scenario of pattern

1. When you want to use various algorithm variants in the object and want to switch algorithms at run time, you can use the policy mode.

Policy mode allows you to indirectly change the behavior of objects at run time by associating them with different sub objects that can perform specific sub tasks in different ways.

2. Use the policy pattern when you have many similar classes that are only slightly different when performing certain behaviors.

You can reduce the repetition of the original code into the same hierarchy, so that you can reduce the repetition of the original code into the same class.

3. If the algorithm is not particularly important in the logic of the context, using this pattern can isolate the business logic of the class from the details of its algorithm implementation.

The policy pattern allows you to isolate the code, internal data and dependencies of various algorithms from other code. Different clients can execute the algorithm through a simple interface and switch at run time.

4. This pattern can be used when complex conditional operators are used in a class to switch between different variants of the same algorithm.

The policy pattern extracts all algorithms inherited from the same interface into separate classes, so conditional statements are no longer required. The original object does not implement all variants of the algorithm, but delegates the execution to one of the independent algorithm objects.

Example of policy pattern in JDK

  • For Java util. The call to comparator #compare() comes from Collections#sort()

  • javax.servlet.http.HttpServlet: service ­ () method, and all methods that accept Http ­ Servlet ­ Request and Http ­ Servlet ­ do with Response object as parameter ­ XXX() method.

  • javax.servlet.Filter#doFilter()

Relationship with other modes

  • The interfaces of bridge mode, state mode and policy mode (including adapter mode to some extent) are very similar. In fact, they are all based on a composite pattern -- delegating work to other objects, but each solves different problems. Patterns are not just recipes for organizing code in a specific way. You can also use them to discuss the problems solved by patterns with other developers.

  • Command patterns and policies look similar because both can parameterize objects through certain behaviors. However, their intentions are very different.

    • You can use commands to convert any operation into an object. The parameters of the operation become member variables of the object. You can use transformations to delay the execution of operations, queue operations, save historical commands, or send commands to remote services.

    • On the other hand, policies can often be used to describe different ways to accomplish something, allowing you to switch algorithms in the same context class.

  • Decoration mode allows you to change the appearance of an object, while strategy allows you to change its essence.

  • The template method pattern is based on inheritance mechanism: it allows you to change some algorithms by extending some contents in subclasses. Strategy is based on combination mechanism: you can change some behaviors of objects by providing different strategies for corresponding behaviors. The template method operates at the class level, so it is static. Policies operate at the object level, thus allowing behavior to be switched at run time.

  • The state can be considered as an extension of the policy. Both are based on a combination mechanism: they both change their behavior in different situations by delegating part of their work to "helper" objects. The policy makes these objects completely independent of each other, and they do not know the existence of other objects. However, the state mode does not limit the dependence between specific states, and allows them to change their states under different scenarios.

Template method mode

intention

It defines an algorithm framework in the superclass, which allows subclasses to rewrite specific steps of the algorithm without modifying the structure.

problem

Suppose you are developing a data mining program to analyze company documents. Users need to input documents in various formats (PDF, DOC or CSV) into the program, and the program will try to extract meaningful data from these files and return them to users in a unified format.

The first version of this program only supports DOC files. In the next version, the program can support CSV files. A month later, you "teach" the program to extract data from PDF files.

Data mining classes contain many duplicate codes.

After a while, you find that these three classes contain a lot of similar code. Although the code of these classes dealing with different data formats is completely different, the code of data processing and analysis is almost the same. Wouldn't it be great to remove duplicate code while maintaining the integrity of the algorithm structure?

There is another problem related to the client code using these classes: the client code contains many conditional statements to select the appropriate processing procedure according to different processing object types. If all data processing classes have the same interface or base class, you can remove the conditional statements in the client code and use the polymorphic mechanism to call functions on the processing object.

Solution

The template method pattern suggests that the algorithm should be decomposed into a series of steps, then these steps should be rewritten into methods, and finally these methods should be called successively in the "template method". Steps can be abstract or have some default implementations. In order to use the algorithm, the client needs to provide its own subclasses and implement all the abstract steps. If necessary, you need to rewrite some steps (but this step does not include the template method itself).

Let's consider how to implement the above scheme in data mining applications. We can create a base class for the three parsing algorithms in the figure, which will define template methods that call a series of different document processing steps.

The template method decomposes the algorithm into steps and allows subclasses to override these steps instead of the actual template method.

First, we declare all steps as abstract types and force subclasses to implement these methods themselves. In our example, there are all necessary implementations in the subclass, so we just need to adjust the signatures of these methods to match the methods of the superclass.

Now let's look at how to remove duplicate code. For different data formats, the codes for opening and closing files and extracting and parsing data are different, so there is no need to modify these methods. However, the implementation of other steps such as analyzing raw data and generating reports is very similar, so it can be extracted into the base class so that subclasses can share this code.

As you can see, we have two types of steps:

  • Abstract steps must be implemented by subclasses
  • Optional steps have some default implementations, but can still be overridden when needed

There is another step called hook. Hook is an optional step with empty content. The template method works even without rewriting the hook. Hooks are usually placed before and after important steps of the algorithm to provide additional algorithm extension points for subclasses.

Real world analogy

Typical architectural schemes can be fine tuned to better meet customer needs.

The formwork method can be used to build a large number of houses. Several extension points can be provided in the standard housing construction scheme to allow potential homeowners to adjust some details of the finished house.

Each construction step (such as laying foundation, building frame, building walls and installing water and electricity pipelines) can be fine tuned, which makes the finished house slightly different.

Pattern structure

Abstract class ­ Class) will declare the methods as algorithm steps and the actual template methods that call them in turn. Some abstract types can be declared or implemented by default.

Concrete ­ Class) can override all steps, but cannot override the template method itself.

General code implementation

public class TemplateMethodPatterDemo {

   public static void main(String[] args) {
      DiscountCalculator calculator1 = new DiscountCalculator1();
      calculator1.calculate();
      
      DiscountCalculator calculator2 = new DiscountCalculator2();
      calculator2.calculate();
      
      DiscountCalculator calculator3 = new DiscountCalculator3();
      calculator3.calculate();
   }
   
   public interface DiscountCalculator {
      void calculate();
   }
   
   public static abstract class AbstractDiscountCalculator implements DiscountCalculator {
      
      @Override
      public void calculate() {
         // Complete general calculation logic
         commonCalculate();
         // Complete special calculation logic
         specificCalculate();
      }
      
      private void commonCalculate() {
         System.out.println("The general calculation logic has been modified");
      }
      
      protected abstract void specificCalculate();
      
   }
   
   public static class DiscountCalculator1 extends AbstractDiscountCalculator {
      @Override
      public void specificCalculate() {
         System.out.println("Special calculation logic of discount calculator 1");  
      }
   }
   
   public static class DiscountCalculator2 extends AbstractDiscountCalculator {
      @Override
      public void specificCalculate() {
         System.out.println("Special calculation logic of discount calculator 2");  
      }
   }
   
   public static class DiscountCalculator3 extends AbstractDiscountCalculator {
      @Override
      public void specificCalculate() {
         System.out.println("Special calculation logic of discount Calculator 3");  
      }
   }
   
}

Standard steps for rewriting algorithms

Usage example: template method patterns are common in Java frameworks. Developers usually use it to provide framework users with a simple way to extend standard functions through inheritance.

In this example, the template method pattern defines an algorithm that can collaborate with social networks. Subclasses that match a particular social network will implement these steps according to the API provided by the social network.

Basic social networks

/**
 * Base class of social network.
 */
public abstract class Network {
    String userName;
    String password;

    Network() {}

    /**
     * Publish the data to whatever network.
     */
    public boolean post(String message) {
        // Authenticate before posting. Every network uses a different
        // authentication method.
        if (logIn(this.userName, this.password)) {
            // Send the post data.
            boolean result =  sendData(message.getBytes());
            logOut();
            return result;
        }
        return false;
    }

    abstract boolean logIn(String userName, String password);
    abstract boolean sendData(byte[] data);
    abstract void logOut();
}

Specific social networks

/**
 * Class of social network
 */
public class Facebook extends Network {
    public Facebook(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public boolean logIn(String userName, String password) {
        System.out.println("\nChecking user's parameters");
        System.out.println("Name: " + this.userName);
        System.out.print("Password: ");
        for (int i = 0; i < this.password.length(); i++) {
            System.out.print("*");
        }
        simulateNetworkLatency();
        System.out.println("\n\nLogIn success on Facebook");
        return true;
    }

    public boolean sendData(byte[] data) {
        boolean messagePosted = true;
        if (messagePosted) {
            System.out.println("Message: '" + new String(data) + "' was posted on Facebook");
            return true;
        } else {
            return false;
        }
    }

    public void logOut() {
        System.out.println("User: '" + userName + "' was logged out from Facebook");
    }

    private void simulateNetworkLatency() {
        try {
            int i = 0;
            System.out.println();
            while (i < 10) {
                System.out.print(".");
                Thread.sleep(500);
                i++;
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

Another social network

/**
 * Class of social network
 */
public class Twitter extends Network {

    public Twitter(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public boolean logIn(String userName, String password) {
        System.out.println("\nChecking user's parameters");
        System.out.println("Name: " + this.userName);
        System.out.print("Password: ");
        for (int i = 0; i < this.password.length(); i++) {
            System.out.print("*");
        }
        simulateNetworkLatency();
        System.out.println("\n\nLogIn success on Twitter");
        return true;
    }

    public boolean sendData(byte[] data) {
        boolean messagePosted = true;
        if (messagePosted) {
            System.out.println("Message: '" + new String(data) + "' was posted on Twitter");
            return true;
        } else {
            return false;
        }
    }

    public void logOut() {
        System.out.println("User: '" + userName + "' was logged out from Twitter");
    }

    private void simulateNetworkLatency() {
        try {
            int i = 0;
            System.out.println();
            while (i < 10) {
                System.out.print(".");
                Thread.sleep(500);
                i++;
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

Client code

/**
 * Demo class. Everything comes together here.
 */
public class Demo {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        Network network = null;
        System.out.print("Input user name: ");
        String userName = reader.readLine();
        System.out.print("Input password: ");
        String password = reader.readLine();

        // Enter the message.
        System.out.print("Input message: ");
        String message = reader.readLine();

        System.out.println("\nChoose social network for posting message.\n" +
                "1 - Facebook\n" +
                "2 - Twitter");
        int choice = Integer.parseInt(reader.readLine());

        // Create proper network object and send the message.
        if (choice == 1) {
            network = new Facebook(userName, password);
        } else if (choice == 2) {
            network = new Twitter(userName, password);
        }
        network.post(message);
    }
}

Application scenario of pattern

1. Use the template method pattern when you only want the client to extend a specific algorithm step rather than the entire algorithm or its structure.

The template method transforms the whole algorithm into a series of independent steps, so that the subclass can extend it, and keep the structure defined in the superclass intact.

2. You can use this pattern when the algorithms of multiple classes are almost the same except for some slight differences. But the consequence is that as long as the algorithm changes, you may need to modify all classes.

When converting an algorithm to a template method, you can extract similar implementation steps into superclasses to remove duplicate code. Different codes between subclasses can continue to remain in subclasses.

JDK method in template:

  • java.util.concurrent.locks.AbstractQueuedSynchronizer

  • java.io.InputStream, java.io.OutputStream, java.io.Reader and Java io. All non abstract methods of writer.

  • java.util.AbstractList, java.util.AbstractSet and Java util. All non abstract methods of abstractmap.

  • javax.servlet.http.HttpServlet, all do's that send HTTP 405 "method not allowed" error response by default ­ XXX() method. You can rewrite it at any time.

Relationship with other modes

  • Factory method pattern is a special form of template method pattern. At the same time, the factory method can be used as a step in a large template method.

  • Template method is based on inheritance mechanism: it allows you to change some algorithms by extending some contents in subclasses. The policy pattern is based on the combination mechanism: you can change part of the object's behavior by providing different strategies for the corresponding behavior. The template method operates at the class level, so it is static. Policies operate at the object level, thus allowing behavior to be switched at run time.

Visitor mode

intention

It can isolate the algorithm from the object it works on.

problem

Suppose your team develops an application that can use geographic information in giant images. Each node in the image can represent both complex entities (such as a city) and finer objects (such as industrial areas and tourist attractions). If there are roads between the real objects represented by nodes, these nodes will be connected to each other. Inside the program, the type of each node is represented by its class, and each specific node is an object.

Export the image as XML.

After a period of time, you receive the task of exporting the image to an XML file. At first, these tasks seem very simple. You plan to add an export function for each node class, and then recursively execute the export function of each node in the image. The solution is simple and elegant: using polymorphism mechanism can make the calling code of exported method not coupled with specific node classes.

Unfortunately for you, the system architect refused to approve changes to existing node classes. He believes that the code is already a product and doesn't want to risk modifying it, because the modification may introduce potential defects.

Methods exported to XML files must be added to the classes of all nodes, but if any defects are introduced in the process of modifying the code, the whole program will be at risk.

In addition, he questioned whether it made sense to include the code to export XML files in node classes. The main work of these classes is to process geographic data. The code for exporting XML files is not appropriate here.

Another reason is that after this task is completed, the marketing department is likely to ask the program to provide the function of exporting other types of files, or make other strange requirements. In this way, you may be forced to modify these important but fragile classes again.

Solution

Visitor pattern suggests putting new behavior into a separate class called visitor rather than trying to integrate it into existing classes. Now, the original object that needs to perform the operation will be passed as a parameter to the method in the visitor, so that the method can access all the necessary data contained in the object.

What if the operation could now be performed on objects of different classes? For example, in our example, the actual implementation of exporting XML files by node classes is likely to be slightly different. Therefore, a visitor class can define a set of methods instead of one, and each method can receive different types of parameters.

But how on earth should we call these methods (especially in dealing with the whole image)? The signatures of these methods are different, so we can't use polymorphic mechanism. In order to pick out visitor methods that can handle specific objects, we need to check its classes. Does this sound like a nightmare?

You might ask, why don't we use method overloading? Is to use the same method name, but their parameters are different. Unfortunately, even if our programming languages (such as Java and C#) support overloading, it won't work. Since we cannot know the class of the node object in advance, the overload mechanism cannot execute the correct method. Method takes the node base class as the default type of the input parameter.

However, visitor mode can solve this problem. It uses a technique called double dispatch, which can execute the correct method without cumbersome conditional statements. Rather than letting the client choose to call the correct version of the method, delegate the choice to the object passed to the visitor as a parameter. Because the object knows its own class, it can more naturally choose the correct method among visitors. Visitors will "receive" them and execute a method.

Now, if we extract the common interface of all visitors, all existing nodes can interact with any visitors we introduce into the program. If you need to introduce a behavior related to the node, you only need to implement a new visitor class.

Real world analogy

Excellent insurance agents can always provide different policies for different types of groups.

If there is such a senior insurance agent who is very eager to win new customers. He can visit every building in the block and try to sell insurance to every passer-by. Therefore, according to the different types of organizations in the building, he can provide special insurance policies:

  • If the building is a residential building, he will sell medical insurance.
  • If the building is a bank, he will sell theft insurance.
  • If the building is a cafe, he will promote fire and flood insurance.

Pattern structure

The Visitor interface declares a series of Visitor methods with specific elements of the object structure as parameters. If the programming language supports overloading, the names of these methods can be the same, but their parameters must be different.

Concrete Visitor will implement several different versions of the same behavior for different concrete element classes.

The Element interface declares a method to "receive" visitors. The method must have a parameter declared as a visitor interface type.

The Concrete Element must implement the receiving method. The purpose of this method is to redirect its call to the method of the corresponding visitor according to the current element class. Note that even if the element base class implements this method, all subclasses must override it and call the appropriate method in the visitor object.

The Client is usually represented as a collection or other complex object, such as a composite tree. Clients are usually unaware of all the concrete element classes because they interact with objects in the collection through abstract interfaces.

General code implementation

public class VisitorPattern {
    public static void main(String[] args) {
        ObjectStructure os = new ObjectStructure();
        os.add(new ConcreteElementA());
        os.add(new ConcreteElementB());
        Visitor visitor = new ConcreteVisitorA();
        os.accept(visitor);
        System.out.println("------------------------");
        visitor = new ConcreteVisitorB();
        os.accept(visitor);
    }
}

//Abstract Visitor 
interface Visitor {
    void visit(ConcreteElementA element);

    void visit(ConcreteElementB element);
}

//Specific visitor class A
class ConcreteVisitorA implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("Specific visitors A visit-->" + element.operationA());
    }

    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("Specific visitors A visit-->" + element.operationB());
    }
}

//Specific visitor class B
class ConcreteVisitorB implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("Specific visitors B visit-->" + element.operationA());
    }

    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("Specific visitors B visit-->" + element.operationB());
    }
}

//Abstract element class
interface Element {
    void accept(Visitor visitor);
}

//Specific element class A
class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operationA() {
        return "Specific elements A Operation of.";
    }
}

//Specific element class B
class ConcreteElementB implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operationB() {
        return "Specific elements B Operation of.";
    }
}

//Object structure role
class ObjectStructure {
    private List<Element> list = new ArrayList<Element>();

    public void accept(Visitor visitor) {
        Iterator<Element> i = list.iterator();
        while (i.hasNext()) {
            ((Element) i.next()).accept(visitor);
        }
    }

    public void add(Element element) {
        list.add(element);
    }

    public void remove(Element element) {
        list.remove(element);
    }
}

Collocation and combination mode

Visitor mode is used in conjunction with composite mode. Because the "element object" in the visitor mode may be a leaf object or a container object, if the element object contains a container object, the combination mode must be used. Its structure diagram is as follows:

public class VisitorPatternDemo {
   
   public static void main(String[] args) {
      Department leafDept1 = new Department("Leaf Department 1"); 
      Department leafDept2 = new Department("Leaf Department 2");
      Department leafDept3 = new Department("Leaf department 3"); 
      
      Department subDept1 = new Department("Sub sector 1");
      subDept1.getChildren().add(leafDept1);
      subDept1.getChildren().add(leafDept2);
      
      Department subDept2 = new Department("Sub sector 2"); 
      subDept2.getChildren().add(leafDept3);
      
      Department parentDept = new Department("Parent department");
      parentDept.getChildren().add(subDept1);
      parentDept.getChildren().add(subDept2);
      
      Visitor removeVisitor = new RemoveVisitor();
      parentDept.accept(removeVisitor);
      
      Visitor updateStatusVisitor = new UpdateStatusVisitor("Disable");  
      parentDept.accept(updateStatusVisitor);
   }
   
   public static class Department {
      private String name;
      private List<Department> children = new ArrayList<Department>();
      
      public Department(String name) {
         super();
         this.name = name;
      }
      
      public String getName() {
         return name;
      }
      public void setName(String name) {
         this.name = name;
      }
      public List<Department> getChildren() {
         return children;
      }
      public void setChildren(List<Department> children) {
         this.children = children;
      }
      
      public void accept(Visitor visitor) {
         visitor.visit(this);
      }
      
   }
   
   public interface Visitor {
      void visit(Department dept);
   }
   
   public static class RemoveVisitor implements Visitor {
      @Override
        public void visit(Department dept) {
         if(dept.getChildren().size() > 0) {
            for(Department child : dept.getChildren()) {  
               child.accept(this);  
            }
         }
         System.out.println("Delete Department[" + dept.getName() + "]");  
      }
      
   }  
   
   public static class UpdateStatusVisitor implements Visitor {
      private String status;
      @Override
        public void visit(Department dept) {
         if(dept.getChildren().size() > 0) {
            for(Department child : dept.getChildren()) {  
               child.accept(this);  
            }
         }
         System.out.println("Will Department[" + dept.getName() + "]The status of is changed to:" + status);  
      }
      
      public UpdateStatusVisitor(String status) {
         this.status = status;
      }
   }
   
}

Application scenario of pattern

1. If you need to perform certain operations on all elements in a complex object structure (such as an object tree), you can use visitor mode.

Visitor mode allows you to perform the same operation on a group of objects belonging to different classes by providing variants of the same operation for multiple target classes in the visitor object.

2. The visitor pattern can be used to clean up the business logic of auxiliary behavior.

This pattern will extract all non main behaviors into a group of visitor classes, so that the main classes of the program can focus more on the main work.

3. Use this pattern when a behavior is meaningful only in some classes in the class hierarchy and not in others.

You can extract this behavior into a separate visitor class, just implement the visitor method that receives the object of the relevant class as a parameter and leave other methods blank.

Visitor pattern example in JDK

  • javax. lang.model. element. Annotation and annotation value ­ Value ­ Visitor
  • javax.lang.model.element.Element and element ­ Visitor
  • javax.lang.model.type.TypeMirror and Type ­ Visitor
  • java.nio.file.FileVisitor and Simple ­ File ­ Visitor
  • javax.faces.component.visit.VisitContext and Visit ­ Callback

Relationship with other modes

  • You can think of visitor mode as an enhanced version of command mode. Its objects can perform operations on a variety of objects of different classes.

  • You can use visitors to perform operations on the entire composite pattern tree.

  • You can use both visitor and iterator patterns to traverse complex data structures and perform the required operations on the elements in them, even if they belong to completely different classes.

Tags: Design Pattern

Posted by gargoylemusic on Fri, 13 May 2022 12:42:04 +0300