Cabbage Java self study room covers core knowledge

The path of The Java Engineer Design pattern (1) The path of the Java Engineer design pattern (2) the path of the Java engineer design pattern (3)

Behavior patterns

Behavioral patterns are responsible for effective communication and delegation of responsibilities between objects.

1. Chain of responsibility mode

Also known as: Chain of Responsibility mode, Chain of Command, CoR, Chain of Command, Chain of Responsibility

The chain of responsibility pattern is a behavior design pattern that allows you to send requests down a chain of handlers. Upon receipt of the request, each handler can either process it or pass it on to the next handler on the chain.

1.1. Fit the scenario

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

This pattern can connect multiple processors into a chain. Upon receiving the request, it “asks” each handler if they can process it. This gives all handlers the opportunity to process the request.

  1. This pattern can be used when multiple handlers must be executed in sequence.

Regardless of the order in which you join the handlers into a chain, all requests will pass through the handlers on the chain in exactly the same order.

  1. If the desired handler and its order must change at run time, the chain of responsibility pattern can be used.

If there are methods for referencing member variables in the handler class, you can dynamically insert and remove handlers, or change their order.

1.2. Implementation method

  1. A signature that declares the handler interface and describes how the request is handled.
  • Determine how the client passes the request data to the method. The most flexible approach is to turn the request into an object and then pass it to the handler as a parameter.
  1. To eliminate duplicate sample code in a concrete handler, you can create an abstract handler base class based on the handler interface.
  • This class requires a member variable to store a reference to the next handler in the chain. You can make it immutable. But if you want to change the chain at run time, you need to define a setting method to change the value of the reference member variable.
  • For ease of use, you can also implement the default behavior of the processing methods. If there are any objects left, the method passes the request to the next object. The concrete handler can also use this behavior by calling methods on the parent object.
  1. Subclass the concrete handler in turn and implement its handling methods. Each handler must make two decisions after receiving the request:
  • Whether to process the request itself.
  • Whether to pass the request down the chain.
  1. The client can assemble the chain itself, or obtain pre-assembled chains from other objects. In the latter case, you must implement the factory class to create chains based on configuration or environment Settings.

  2. The client can trigger any handler in the chain, not just the first one. The request passes through the chain until either a handler refuses to continue or the request reaches the end of the chain.

  3. Due to the dynamic nature of chains, clients need to be prepared to handle the following situations:

  • There may be only a single link in the chain.
  • Some requests may not reach the end of the chain.
  • Other requests may not be processed until the end of the chain.

1.3. Pattern structure

  1. Handlers declare 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 that sets the next handler in the chain.
  2. Base Handler is an optional class in which you can place sample code shared by all handlers. Typically, this class defines a member variable that holds a reference to the next handler. A client can create a chain by passing a handler to the previous handler’s constructor or setting method. This class can also implement the default processing behavior of passing requests to the next handler after confirming that it exists.
  3. Concrete Handlers contain the actual code that handles the request. Once each handler receives the request, it must decide whether to process it and whether to pass the request down the chain. Handlers are usually independent and immutable, requiring all the necessary data to be obtained at once through constructors.
  4. The Client can generate the chain either once or dynamically, depending on the program logic. Note that the request can be sent to any handler on the chain, not necessarily to the first handler.

1.4. Advantages and disadvantages

  1. You can control the order in which requests are processed.
  2. Single responsibility principle. You can decouple the class that initiates the operation from the class that performs it.
  3. Open and close principle. You can add handlers to your program without changing the existing code.
  • Some requests may not be processed.

1.5. Relationship with other modes

  1. The chain of responsibility pattern, command pattern, mediator pattern, and observer pattern are used to handle the different connections between request senders and receivers:
  • The chain of responsibility dynamically passes requests in sequence to a series of potential recipients until one of them processes the request.
  • The command establishes a one-way connection between a sender and a requester.
  • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
  • The observer allows the receiver to dynamically subscribe or unsubscribe from receiving requests.
  1. Chains of responsibility are often used in conjunction with the composite pattern. In this case, the leaf component receives the request and passes it down the chain of all the parent components to the bottom of the object tree.

  2. The manager of the chain of responsibility can be implemented using the command pattern. In this case, you can perform many different operations on the same context object represented by the request. Another way to do this is if 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.

  3. The responsibility chain is very similar to the class structure of the decorator pattern. Both rely on recursive composition to pass operations that need to be performed to a series of objects. However, there are several important differences. The managers of the chain of responsibility can perform everything independently of each other and can stop passing requests at any time. Decorations, on the other hand, extend the behavior of an object while following the basic interface. In addition, decoration cannot interrupt the delivery of requests.

2. Command mode

Also called: Action, Transaction, Action, Transaction, Command

Command pattern is a behavior design pattern that transforms a request into a single object that contains all the information associated with the request. This transformation allows you to parameterize methods, delay request execution, or queue them based on different requests, and implement undoable operations.

2.1. Applicable to the scenario

  1. If you need to parameterize an object by action, use command mode.

The command pattern transforms a particular method call into a stand-alone object. This change also opens up a number of interesting applications: you can pass commands as arguments to methods, save commands in other objects, or switch connected commands at run time, etc.

For example, if you are developing a GUI component (such as a context menu), you want users to be able to configure menu items and trigger actions when they are clicked.

  1. You can use command mode if you want to queue operations, execute operations, or execute operations remotely.

Like any other object, commands can be serialized (serialized means converted to a string) so that they can be easily written to a file or database. After some time, the string can be restored to the original command object. Therefore, you can delay or schedule the execution of the command. But it’s much more than that! In the same way, you can queue commands, log commands, or send commands over the network.

  1. If you want to implement rollback, use command mode.

Although there are many ways to implement undo and restore functionality, command mode is probably one of the most common.

In order to be able to roll back actions, you need to implement a history of actions that have been performed. Command history is a stack structure that contains a backup of all executed command objects and their associated program state.

There are two drawbacks to this approach. First, the ability to save program state is not easy to implement, because some state may be private. You can use the memo pattern to solve this problem to some extent.

Second, the backup state can take up a lot of memory. Therefore, sometimes you need to resort to another implementation: instead of restoring the original state, the command does the reverse. Doing the opposite has a cost: it can be difficult or even impossible.

2.2. Implementation method

  1. Declare a single command interface that executes a method.

  2. Extract the request and make it into a concrete command class that implements the command interface. Each class must have a set of member variables that hold the request parameters and references to the actual recipient object. The values of all these variables must be initialized by the command constructor.

  3. Find the class that acts as the sender’s responsibility. Add member variables to these classes that hold the command. Senders can only interact with their commands through the command interface. Senders typically do not create command objects themselves, but rather get them through client code.

  4. Modify the sender to execute the command instead of sending the request directly to the receiver.

  5. Clients must initialize objects in the following order:

  • Create the receiver.
  • Create a command that can be associated to the recipient if needed.
  • Create a sender and associate it with a specific command.

2.3. Pattern structure

  1. The Sender — also known as the Invoker — class is responsible for initializing the request and must contain a member variable to store a reference to the command object. The sender fires 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.
  2. A Command interface usually declares only a method that executes a Command.
  3. Concrete Commands implement various types of requests. The concrete command does not do the work by itself, but rather delegates the invocation to a business logic object. But to simplify the code, these classes can be merged. The parameters required by the receiving object to execute a method can be declared as member variables of a specific command. You can set the command object to be immutable and only allow these member variables to be initialized through constructors.
  4. The Receiver class contains part of the business logic. Almost any object can be a receiver. Most commands deal only with the details of how to deliver the request to the recipient, who does the actual work themselves.
  5. The Client creates and configures specific command objects. The client must pass all the request parameters, including the receiver entity, to the command’s constructor. After that, the generated command can be associated with one or more senders.

2.4. Advantages and disadvantages

  1. Single responsibility principle. You can decouple the classes that trigger and perform operations.
  2. Open and close principle. You can create new commands in your program without modifying existing client code.
  3. You can implement undo and restore.
  4. You can implement deferred execution of operations.
  5. You can combine a set of simple commands into a complex command.
  • The code may become more complex because you add a whole new layer between sender and receiver.

2.5. Relationship with other modes

  1. The chain of responsibility pattern, command pattern, mediator pattern, and observer pattern are used to handle the different connections between request senders and receivers:
  • The chain of responsibility dynamically passes requests in sequence to a series of potential recipients until one of them processes the request.
  • The command establishes a one-way connection between a sender and a requester.
  • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
  • The observer allows the receiver to dynamically subscribe or unsubscribe from receiving requests.
  1. The manager of the chain of responsibility can be implemented using the command pattern. In this case, you can perform many different operations on the same context object represented by the request.

Another way to do this is if 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.

  1. You can do undo using both command and memo modes. In this case, commands are used to perform various operations on the target object, and memos are used to hold the state of the object before a command is executed.

  2. The command and policy patterns look similar because both can parameterize objects through certain behaviors. But their intentions are very different.

  • You can use commands to convert any operation into an object. The parameters of the operation will become member variables of the object. You can use transformations to delay the execution of operations, queue operations, save history commands, or send commands to remote services.
  • Policies, on the other hand, can often be used to describe different ways of doing something, allowing you to switch algorithms within the same context class.
  1. Prototype mode can be used to keep a history of commands.

  2. You can think of the visitor pattern as an enhanced version of the command pattern, where objects can perform operations on multiple objects of different classes.

3. Iterator pattern

Also known as: the Iterator

The iterator pattern is a behavioral design pattern that lets you iterate through all the elements of a collection without exposing the underlying representation of the collection (lists, stacks, trees, etc.).

3.1. Fit the scene

  1. The iterator pattern can be used when there is a complex data structure behind the collection and you want to hide the complexity from the client (for ease of use or security reasons).

Iterators encapsulate the details of interacting with complex data structures, providing clients with multiple simple ways to access collection elements. This approach is not only convenient for the client, but also protects the collection from doing something wrong or harmful when the client interacts directly with the collection.

  1. Using this pattern can reduce repeated traversal code in the program.

The code for important iterative algorithms is often very bulky. When this code is placed in the program business logic, it obfuscates the responsibilities of the original code, reducing its maintainability. Therefore, moving the traversal code to a specific iterator makes the program code more concise and concise.

  1. If you want your code to iterate over 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 fine when you pass it other collections and iterators that implement these interfaces.

3.2. Implementation method

  1. Declare the iterator interface. The interface must provide at least one method to get the next element in the collection. But for ease of use, you can add other methods, such as getting the previous element, recording the current position, and determining whether the iteration has ended.
  2. Declares the collection interface and describes a method for getting iterators. The return value must be an iterator interface. If you plan to have multiple different sets of iterators, you can declare multiple similar methods.
  3. Implement concrete iterator classes for collections that you want to traverse using iterators. An iterator object must be linked to a single collection entity. Link relationships are usually established through iterator constructors.
  4. Implement the collection interface in your collection class. The main idea is to provide a shortcut for client code to create iterators for a particular collection. The collection object must pass itself to the iterator’s constructor to create a link between the two.
  5. Examine the client code and replace all collection traversal code with iterators. A new iterator is obtained each time the client needs to traverse a collection element.

3.3. Pattern structure

  1. The Iterator interface declares the operations needed to traverse a collection: get the next element, get the current position, restart the iteration, and so on.
  2. Concrete Iterators a specific algorithm that implements traversal of a set. An iterator object must track the progress of its own traversal. This allows multiple iterators to traverse the same collection independently of each other.
  3. The Collection interface declares one or more methods to get iterators that are compatible with collections. Note that the type of the return method must be declared as an iterator interface, so concrete collections can return a variety of iterators.
  4. Concrete Collections returns a specific Concrete iterator class entity when a client requests an iterator. You might be wondering, where is the rest of the collection code? Don’t worry, it will be in the same class. These details are just not important to the actual schema, so we’ve left them out.
  5. Clients interact with both collections and iterators through their interfaces. This eliminates the need for the client to be coupled to a concrete class, allowing the same client code to use a variety of different collections and iterators. Clients typically do not create iterators themselves, but rather fetch them from collections. However, in certain cases, the client can create an iterator directly (for example, when the client needs to customize a special iterator).

3.4. Advantages and disadvantages

  1. Single responsibility principle. By extracting the bulky traversal code into separate classes, you can tidy up the client code and collections.
  2. Open and close principle. You can implement new types of collections and iterators and pass them to existing code without modifying existing code.
  3. You can walk through the same collection side by side, because each iterator object contains its own traversal state.
  4. Similarly, you can pause traversal and continue as needed.
  • If your program only interacts with simple collections, applying this pattern may overcompensate.
  • For some special sets, iterators may be less efficient than direct traversal.

3.5. Relationship with other modes

  1. You can use the iterator pattern to traverse the composite pattern tree.
  2. You can use both the factory method pattern and iterators to make the subclass collection return different types of iterators and match the iterators to the collection.
  3. You can use both memo mode and iterators to get the state of the current iterator and roll it back as needed.
  4. You can use both visitor patterns and iterators to traverse complex data structures and perform desired operations on elements within them, even if they belong to completely different classes.

4. The Intermediary model

Also known as: Mediator, Controller, Intermediary, Controller, Mediator

The mediator pattern is a behavior design pattern that allows you to reduce chaotic dependencies between objects. This pattern restricts direct interaction between objects, forcing them to collaborate through a mediator object.

4.1. Fit the scene

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

This pattern lets you extract all relationships between objects into a single class so that changes to a particular component are made independently of other components.

  1. The mediator pattern can be used when components are too dependent on other components to be replicated across different applications.

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

  1. Use the mediator pattern if you are forced to create a large number of component subclasses in order to be able to reuse some basic behavior in different scenarios.

Because all the relationships between components are contained in the mediator, you can easily create a new mediator class to define a new component collaboration without modifying the component.

4.2. Implementation method

  1. Find a set of classes that are currently tightly coupled and offer greater benefits (such as easier maintenance or reuse) from their independent performance.
  2. Declare the mediator interface and describe the communication interface required between the mediator and the various components. In most cases, a method to receive component notifications is sufficient. This interface is important if you want to reuse component classes in different scenarios. As long as a component uses a common interface to work with its mediator, you can connect that component to intermediaries in different implementations.
  3. Implement the concrete intermediary class. This class benefits from keeping references to all the components under it.
  4. You can go one step further and have the mediator responsible for the creation and destruction of component objects. Thereafter, the intermediary may resemble a factory or look.
  5. The component must save a reference to the mediator object. This connection is usually established in the component’s constructor, which passes the mediator object as a parameter.
  6. Modify the component code so that it can invoke the intermediary’s notification method instead of the other component’s method. The code that calls other components is then extracted into the mediator class and executed when the mediator receives a notification of that component.

4.3. Pattern structure

  1. Components are 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 which class the mediator actually belongs to, so you can reuse it in other programs by wiring it to different intermediaries.
  2. The Mediator interface declares a method for communicating with a component, but usually includes only a notification method. A component can take any context (including its own object) as an argument to this method so that there is no coupling between the receiving component and the sender class.
  3. Concrete Mediators encapsulate the relationships between various components. Specific intermediaries typically save references to all components and manage them, sometimes even their life cycle.
  4. Components do not know about other components. If an important event occurs within a component, it can only notify the mediator. The intermediary can easily identify the sender after receiving the notification, which may be enough to determine which components need to be triggered next. To a component, the mediator looks like a complete black box. The sender does not know who will end up handling his request, and the receiver does not know who made the request in the first place.

4.4. Advantages and disadvantages

  1. Single responsibility principle. You can extract communication between multiple components into one location, making it easier to understand and maintain.
  2. Open and close principle. You can add new intermediaries without modifying the actual components.
  3. You can reduce coupling between multiple components in your application.
  4. You can reuse components more easily.
  • Over time, mediators may evolve into objects of God.

4.5. Relationship with other modes

  1. The chain of responsibility pattern, command pattern, mediator pattern, and observer pattern are used to handle the different connections between request senders and receivers:
  • The chain of responsibility dynamically passes requests in sequence to a series of potential recipients until one of them processes the request.
  • The command establishes a one-way connection between a sender and a requester.
  • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
  • The observer allows the receiver to dynamically subscribe or unsubscribe from receiving requests.
  1. Appearance patterns and intermediaries have similar responsibilities: they both try to organize collaboration among a large number of tightly coupled classes.
  • The facade defines a simple interface for all objects in the subsystem, but it does not provide any new functionality. The subsystem itself is not aware of the appearance. Objects in a subsystem can communicate directly with each other.
  • Intermediaries centralize the communication behavior of components in a system. Components only know the intermediary object and cannot communicate with each other directly.
  1. The distinction between mediator and observer is often hard to remember. In most cases, you can use one or the other, and sometimes both. Let’s look at how to do this.
  • The primary goal of the mediator is to remove interdependencies between a set of system components. These components will depend on the same mediator object. The goal of the observer is to establish dynamic one-way connections between objects so that some objects can function as appendages to others.
  • A popular implementation of the mediator pattern relies 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 this way, it can look very much like an observer.
  • When in doubt, remember that there are other ways to implement a mediator. For example, you can permanently link all components to the same mediator object. This is not implemented in the same way as an observer, but it is still a mediator pattern.
  • Imagine a program where all of its components become publishers, and they can dynamically connect to each other. There are no central mediator objects in the program, but only distributed observers.

5. Memo mode

Also known as Snapshot, Snapshot, Memento

The memo pattern is a behavioral design pattern that allows an object to be saved and restored to its previous state without exposing its implementation details.

5.1. Applicable to the scenario

  1. Use the memo mode when you need to create a snapshot of an object’s state to restore its previous state.

Memo mode allows you to copy all the state in an object (including private member variables) and save it independently of the object. Although most people remember this pattern because of the “undo” use case, it is also essential when dealing with transactions, such as needing to roll back an operation in the event of an error.

  1. This pattern can be used when direct access to an object’s member variable, getter, or setter would cause encapsulation to be broken.

Memos leave objects responsible for creating snapshots of their state. No other object can read the snapshot, effectively ensuring data security.

5.2. Implementation method

  1. Identify the class that acts as the primary. It is important to clarify whether the program uses a single originator center object or multiple smaller objects.
  2. Create the memo class. Declare the memo member variables corresponding to each original member variable one by one.
  3. Make the memo class immutable. Memos can only receive data once through constructors. The class cannot contain a setter.
  4. If your programming language supports nested classes, you can nest memos in the originator. If not, you can extract an empty interface from the memo class and have all other objects reference the memo through the interface. You can add metadata operations to this interface, but you cannot expose the state of the originator.
  5. Add a method to create memos in the originator. The originator must pass its state to the memo through one or more actual arguments to the memo constructor. The method must return the type of the interface you extracted in the previous step (if you already extracted it). In fact, the method that creates the memo must interact directly with the memo class.
  6. Add a method to the originator class that restores its state. The method takes a memo object as an argument. If you extracted the interface in the previous step, use the interface as the type of the parameter. In this case, you need to cast the input object into a memo, because the originator needs to have full access to the object.
  7. Whether the owner is a command object, a history, or something completely different, it must know when to request new memos from the originator, how to store memos, and when to use specific memos to restore the originator.
  8. The connection between the owner and the originator can be moved to the memo class. In this case, each memo must be connected to the originator that created its own. The restore method can also be moved to the memo class, but only if the memo class is nested in the originator or if the originator class provides enough setters to override its state.

5.3. Pattern structure

5.3.1. Implementation based on nested classes

  1. An Originator class can take snapshots of its state or recover its state if needed.
  2. Memento is a value object of a snapshot of the originator state. It is common practice to make memos immutable and pass the data once through constructors.
  3. The Caretaker only knows “when” and “why” the original state is captured and how to restore the state. The owner keeps a memo stack to record the historical status of the originator. When the originator needs to go back to the historical state, the principal retrieves the topmost memo from the stack and passes it to the restoration method of the originator.
  4. In this implementation, the memo class will be nested in the originator. This gives the originator access to the memo’s member variables and methods, even if those methods are declared private. On the other hand, the principals have very limited access to the member variables and methods of the memo: they can only hold the memo on the stack, not modify its state.

5.3.2. Implementation based on intermediate interface

  1. In the absence of nested classes, you can specify that principals can only interact with memos through explicitly stated intermediate interfaces. This interface declares only methods related to the memo metadata, limiting their direct access to the memo member variables.
  2. On the other hand, the originator can interact directly with the memo object, accessing the member variables and methods declared in the memo class. The downside of this approach is that you need to declare all member variables of the memo to be public.

5.3.3. Encapsulate a more stringent implementation

  1. This implementation allows for many different types of originators and memos. Each originator interacts with its corresponding memo class. Neither originators nor memos expose their state to other classes.
  2. The responsible person is specifically prohibited from modifying the state stored in the memo at this time. But the responsible human will be independent of the originator, because at this point the recovery method is defined in the memo class.
  3. Each memo will connect to the originator that created its own. The originator passes itself and state to the constructor of the memo. Because of the close relationship between these classes, memos can be restored to their state as long as the originator defines the appropriate setters.

5.4. Advantages and disadvantages

  1. You can create snapshots of object state without breaking object encapsulation.
  2. You can simplify the originator code by having the owner maintain a history of the originator state.
  • If the client creates memos too often, the program consumes a lot of memory.
  • The owner must fully track the lifecycle of the originator so that deprecated memos can be destroyed.
  • Most dynamic programming languages (such as PHP, Python, and JavaScript) cannot ensure that the state in a memo is not changed.

5.5. Relationship with other modes

  1. You can use both command mode and memo mode for undo. In this case, commands are used to perform various operations on the target object, and memos are used to hold the state of the object before a command is executed.
  2. You can use both the memo and iterator patterns to get the state of the current iterator and roll it back as needed.
  3. Sometimes the prototype pattern can be used as a simplified version of a memo, if the state of the objects you need to store in the history is simple and there is no need to link to other external resources, or if the link can be easily rebuilt.

6. Observer mode

Also called Event Subscriber, Listener, event-subscriber, Listener, Observer

The Observer pattern is a behavior design pattern that allows you to define a subscription mechanism that notifies multiple other objects that “observe” an object when an event occurs.

6.1. Applicable to the scenario

  1. The observer mode can be used when a change in the state of one object requires changes to other objects, or when the actual object is previously unknown or dynamically changing.

There is usually a problem when you use graphical user interface classes. For example, you create a custom button class and allow the client to inject custom code into the button so that it is triggered when the user presses the button. The Observer pattern allows any object that implements the subscriber interface to subscribe to event notifications of the publisher object. You can add a subscription mechanism in the button that allows clients to inject custom code through custom subscription classes.

  1. This pattern can be used when some objects in an application must observe other objects. But it can only be used for a limited time or under certain circumstances.

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

6.2. Implementation method

  1. Take a close look at your business logic and try to split it into two parts: the core functionality independent of the rest of the code will act as publisher; The rest of the code is converted to a set of subscription classes.

  2. Declare the subscriber interface. The interface should declare at least one update method.

  3. Declare publisher interfaces and define interfaces to add and remove subscription objects from the list. Remember that publishers must interact with them only through the subscriber interface.

  4. Determine where to store the actual subscription list and implement the subscription method. Often all types of publisher code look the same, so it makes sense to place lists in abstract classes that extend directly from the publisher’s interface. The specific publisher extends the class to inherit all subscription behavior. However, if you need to apply the pattern within an existing class hierarchy, consider a composite approach: put the subscription logic into a single object, and then let all the actual subscribers use that object.

  5. Create a concrete publisher class. All subscribers must be notified every time a publisher has an important event.

  6. A method to implement notification updates in a concrete subscriber class. The vast majority of subscribers require some context data related to events. This data can be passed as a parameter to the notification method. But there is another option. The subscriber receives the notification and retrieves all data directly from the notification. In this case, the publisher must pass itself through the update method. Another, less flexible approach is to permanently link publishers and subscribers through constructors.

  7. The client must generate all required subscribers and complete registration with the appropriate publisher.

6.3. Pattern structure

  1. Publishers send noteworthy events to other objects. Events occur after the publisher’s own state changes or certain actions are performed. Publishers include a subscription architecture that allows new subscribers to join and current subscribers to leave the list.
  2. When a new event occurs, the sender iterates through the subscription list and invokes the notification method of each subscriber object. This method is declared in the subscriber interface.
  3. The Subscriber interface declares the notification interface. In most cases, the interface contains only one update method. This method can have multiple parameters that enable the publisher to pass the details of the event at update time.
  4. Concrete Subscribers can perform some operations in response to the publisher’s notification. All concrete subscriber classes implement the same interface, so publishers do not need to be coupled to concrete classes.
  5. Subscribers usually need some context information to properly process updates. As a result, publishers typically pass some context data as parameters to the notification method. Publishers can also pass themselves as parameters, allowing subscribers to get the data they want directly.
  6. The Client creates publisher and subscriber objects, respectively, and registers publisher updates for subscribers.

6.4. Advantages and disadvantages

  1. Open and close principle. You can introduce a new subscriber class without having to change the publisher code (you can easily introduce a publisher class if it’s a publisher interface).
  2. You can establish connections between objects at run time.
  • Subscribers are notified in random order.

6.5. Relationship with other modes

  1. The chain of responsibility pattern, command pattern, mediator pattern, and observer pattern are used to handle the different connections between request senders and receivers:
  • The chain of responsibility dynamically passes requests in sequence to a series of potential recipients until one of them processes the request.
  • The command establishes a one-way connection between a sender and a requester.
  • The mediator clears the direct connection between the sender and the requester, forcing them to communicate indirectly through a mediation object.
  • The observer allows the receiver to dynamically subscribe or unsubscribe from receiving requests.
  1. The distinction between mediator and observer is often hard to remember. In most cases, you can use one or the other, and sometimes both. Let’s look at how to do this.
  • The primary goal of the mediator is to remove interdependencies between a set of system components. These components will depend on the same mediator object. The goal of the observer is to establish dynamic one-way connections between objects so that some objects can function as appendages to others.
  • A popular implementation of the mediator pattern relies 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 this way, it can look very much like an observer.
  • When in doubt, remember that there are other ways to implement a mediator. For example, you can permanently link all components to the same mediator object. This is not implemented in the same way as an observer, but it is still a mediator pattern.
  • Imagine a program where all of its components become publishers, and they can dynamically connect to each other. There are no central mediator objects in the program, but only distributed observers.

7. State mode

Also known as: the State

State mode is a behavior design mode that allows you to change the behavior of an object as its internal state changes, making it look like it’s changing the class it belongs to.

7.1. Applicable to the scenario

  1. State patterns can be used if an object needs to behave differently based on its current state, if the number of states is large and the state-related code changes frequently.

The pattern suggests that you extract all state-specific code into a separate set of classes. This way, you can add new states or modify existing states independently of other states, reducing maintenance costs.

  1. This pattern can be used when a class needs to change its behavior based on the current value of a member variable, requiring a large number of conditional statements.

The state pattern extracts branches of these conditional statements into the methods of the corresponding state class. You can also clean up temporary member variables and helper method code associated with a particular state in the main class.

  1. State patterns can be used when there is a lot of duplicate code in similar states and conditional based state machine transitions.

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

7.2. Implementation method

  1. Determine which classes are contexts. It could be an existing class that contains state-dependent code; If state-specific code is spread across multiple classes, it may be a new class.

  2. Declare the status interface. While you may want to copy exactly all methods declared in the context, it is best to focus only on those methods that may contain state-specific behavior.

  3. Create a class for each actual state that inherits from the state interface. The methods in the context are then examined and all code related to that particular state is extracted into the newly created class. As you move your code to the status class, you may find that it depends on some private members of the context. Here are some workarounds you can use:

  • Make these member variables or methods public.
  • Change the context behavior that you want to extract to a public method in the context and call it in the state class. It’s simple and quick, and you can fix it later.
  • Nested state classes in context classes. This approach requires that your programming language supports nested classes.
  1. Adds a reference member variable of the state interface type and a public setter to modify the value of the member variable to the context class.

  2. Again examine the methods in context and replace the empty conditional statement with the corresponding state object method.

  3. To switch the context state, you need to create an instance of the state class and pass it to the context. You can do this in context, various states, or on the client side. Wherever this is done, the class will depend on the concrete class it instantiates.

7.3. Pattern structure

  1. A Context holds a reference to a concrete state object and delegates all work related to that state to it. The context interacts with the state object through the state interface and provides a setter for passing the new state object.
  2. The State interface declares state-specific methods. These methods should be understood by all other concrete states, because you don’t want methods owned by some states to never be called.
  3. Concrete States implement state-specific methods on their own. To avoid similar code in multiple states, you can provide an intermediate abstract class that encapsulates some common behavior. State objects can store backreferences to context objects. This reference is used by the state to get the information it needs from the context and can trigger a state transition.
  4. Both the context and the concrete state can set the next state of the context, and the actual state transition can be accomplished by replacing the state object connected to the context.

7.4. Advantages and disadvantages

  1. Single responsibility principle. Put the code related to a particular state in a separate class.
  2. Open and close principle. New states can be introduced without modifying existing state classes and contexts.
  3. Simplify context code by eliminating bloated state machine conditional statements.
  • If the state machine has only a few states, or very few changes, applying this pattern can be a fuss.

7.5. Relationship with other modes

  1. The interfaces of bridge mode, state mode, and policy mode (including, to some extent, adapter mode) modes are very similar. In fact, they are both based on a composite pattern — delegating work to other objects, but each solves a different problem. Patterns are not just recipes for organizing code in a particular way; you can also use them to discuss the problems that patterns solve with other developers.

  2. State can be viewed as an extension of a policy. Both are based on composition: they both change the behavior of helper objects in different situations by delegating part of their work to them. The policy makes these objects completely independent of each other and unaware of the existence of other objects. However, the state pattern does not limit the dependencies between specific states and allows them to change their state in different scenarios.

8. Strategic mode

Also said the Strategy

The strategy pattern is a behavior design pattern that allows you to define a series of algorithms and place each algorithm in a separate class so that the objects of the algorithm are interchangeable.

8.1. Fit the scene

  1. Use policy mode when you want to work with various algorithm variants in an object and want to be able to switch algorithms at runtime.

Policy patterns allow you to indirectly change the behavior of objects at run time by associating objects with different child objects that can perform specific subtasks in different ways.

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

The policy pattern allows you to extract different behaviors into a separate class hierarchy and combine the original classes into the same, thereby reducing duplicate code.

  1. If the algorithm is not particularly important in the logic of the context, this pattern can be used to isolate the business logic of the class from its algorithm implementation details.

The policy pattern lets you isolate the code, internal data, and dependencies of various algorithms from other code. Algorithms can be executed by different clients through a simple interface and switched at run time.

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

The policy pattern extracts all algorithms that inherit from the same interface into separate classes, eliminating the need for conditional statements. The original object does not implement all variations of the algorithm, but delegates execution to one of the separate algorithm objects.

8.2. Implementation method

  1. Identify algorithms from context classes that change more frequently (or perhaps complex conditional operators used to select an algorithm variant at run time).

  2. Declare a common policy interface for all variants of the algorithm.

  3. The algorithms are extracted one by one into their respective classes, which must implement the policy interface.

  4. Add a member variable to the context class to hold a reference to the policy object. A setter is then provided to modify the member variable. A context can only interact with a policy object through a policy interface and, if necessary, define an interface for the policy to access its data.

  5. The client must associate the context class with the corresponding policy so that the context can do its main job in the expected way.

8.3. Pattern structure

  1. Context maintains references to specific policies and communicates with that object only through the policy interface.
  2. The Strategy interface is a common interface for all specific policies and declares a context for methods to enforce the policy.
  3. Concrete Strategies implement various variations of the algorithms used in context.
  4. When the context needs to run the algorithm, it calls the execution method on its attached policy object. The context is unclear about the type of policy involved and how the algorithm is executed.
  5. The Client creates a specific policy object and passes it to the context. The context provides a setter for the client to replace the associated policy at run time.

8.4. Advantages and disadvantages

  1. You can switch algorithms within objects at run time.
  2. You can isolate the implementation of an algorithm from the code that uses it.
  3. You can use composition instead of inheritance.
  4. Open and close principle. You can introduce new policies without changing the context.
  • If your algorithm rarely changes, there is no reason to introduce new classes and interfaces. Using this mode just makes your program too complex.
  • The client must know the difference between policies — it needs to choose the right one.
  • Many modern programming languages support function type functionality, which allows you to implement different versions of an algorithm in a set of anonymous functions. This way, you use these functions exactly as you would use policy objects, without resorting to extra classes and interfaces to keep your code simple.

8.5. Relationship with other modes

  1. The interfaces of bridge mode, state mode, and policy mode (including, to some extent, adapter mode) modes are very similar. In fact, they are both based on a composite pattern — delegating work to other objects, but each solves a different problem. Patterns are not just recipes for organizing code in a particular way; you can also use them to discuss the problems that patterns solve with other developers.

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

  • You can use commands to convert any operation into an object. The parameters of the operation will become member variables of the object. You can use transformations to delay the execution of operations, queue operations, save history commands, or send commands to remote services.
  • Policies, on the other hand, can often be used to describe different ways of doing something, allowing you to switch algorithms within the same context class.
  1. Decoration mode lets you change the appearance of an object, while strategy lets you change its essence.

  2. The template method pattern is based on inheritance: it allows you to change part of the algorithm by extending part of the subclass. Policies are based on composition: You can change part of an object’s behavior by providing different policies for corresponding behaviors. A 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.

  3. State can be viewed as an extension of a policy. Both are based on composition: they both change the behavior of helper objects in different situations by delegating part of their work to them. The policy makes these objects completely independent of each other and unaware of the existence of other objects. However, the state pattern does not limit the dependencies between specific states and allows them to change their state in different scenarios.

9. Template method pattern

Also known as Template Method

The template method pattern is a behavior design pattern that defines the framework of an algorithm in a superclass, allowing subclasses to override specific steps of the algorithm without modifying the structure.

9.1. Applicable to the scenario

  1. The template method pattern is used when you only want the client to extend a particular algorithm step, not the entire algorithm or its structure.

The template method translates the entire algorithm into a series of independent steps so that subclasses can extend it, while keeping the structure defined in the superclass intact.

  1. You can use this pattern when the algorithms of multiple classes are almost identical except for some slight differences. The consequence, however, is that whenever the algorithm changes, you may need to modify all the classes.

When converting an algorithm to a template method, you can extract similar implementation steps into a superclass to remove duplicate code. Code that differs from one subclass to another may remain in the subclass.

9.2. Implementation method

  1. Analyze the target algorithm to determine if it can be decomposed into multiple steps. From the perspective of all subclasses, consider which steps are generic and which are different.

  2. Create an abstract base class and declare a template method and a series of abstract methods that represent the steps of the algorithm. The corresponding steps are called in sequence in the template method based on the algorithm structure. Final final decorates template methods to prevent subclasses from overwriting them.

  3. While it is possible to set all steps to abstract types, some steps may benefit from a default implementation because subclasses do not need to implement those methods.

  4. Consider adding hooks between key steps of the algorithm.

  5. Create a concrete subclass for each algorithm variant that must implement all of the abstract steps or override some of the optional steps.

9.3. Pattern structure

  1. An AbstractClass declares methods as algorithmic steps and the actual template methods that call them in turn. Algorithmic steps can be declared as abstract types, or some default implementation can be provided.
  2. The ConcreteClass (ConcreteClass) can override all steps, but not the template method itself.

9.4. Advantages and disadvantages

  1. You can only allow clients to rewrite certain parts of a large algorithm, minimizing the impact of changes to other parts of the algorithm.
  2. You can extract duplicate code into a superclass.
  • Some clients may be limited by the algorithm framework.
  • Subclassing the default step implementation may result in a violation of the reefer substitution principle.
  • The more steps in a template method, the more difficult it can be to maintain.

9.5. Relationships with other patterns

  1. The factory method pattern is a special form of the template method pattern. At the same time, the factory method can be used as a step in a larger template method.

  2. The template approach is based on inheritance: it allows you to change part of the algorithm by extending part of the subclass. Policy patterns are based on composition: you can change part of an object’s behavior by providing different policies for the corresponding behavior. A 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.

10. Visitor pattern

Also said the Visitor

The visitor pattern is a behavior design pattern that isolates an algorithm from the object it works on.

10.1. Fit the scene

  1. If you need to perform certain operations on all elements in a complex object structure, such as a tree of objects, use the Visitor pattern.

The visitor pattern lets you perform the same action on a group of objects belonging to different classes by providing variations of the same action for multiple target classes in the visitor object.

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

This pattern extracts all non-primary behavior into a set of visitor classes, allowing the primary classes of the program to focus more on the primary work.

  1. This pattern can be used when a behavior makes sense only in some classes in a class hierarchy and not in others.

You can extract this behavior into a separate visitor class by implementing a visitor method that takes an object of the related class as a parameter and leaving the other methods blank.

10.2. Implementation method

  1. Declare a set of “access” methods in the visitor interface, one for each concrete element class in the program.

  2. Declare the element interface. If you already have an element class hierarchy interface in your program, you can add an abstract “receive” method to the hierarchy base class. The method must accept a visitor object as a parameter.

  3. Implement receive methods in all concrete element classes. These methods must redirect the call to the visitor method in the visitor object corresponding to the current element.

  4. Element classes can only interact with visitors through the visitor interface. However, the visitor must know all the concrete element classes, because these classes are referenced as parameter types in the visitor method.

  5. Create a concrete visitor class and implement all visitor methods for each behavior that cannot be implemented in an element hierarchy. You may encounter situations where visitors need to access some of the private member variables of an element class. In this case, you can either make these variables or methods public, which breaks element encapsulation; Or embed the visitor class in the element class. The latter approach is only possible in programming languages that support nested classes.

  6. The client must create the visitor object and pass it to the element through the “receive” method.

10.3. Pattern structure

  1. The Visitor interface declares a series of Visitor methods that take specific elements of the object structure as parameters. If the programming language supports overloading, the names of these methods can be the same, but the parameters must be different.
  2. Concrete Visitors implement several versions of the same behavior for different Concrete element classes.
  3. The Element interface declares a method to “receive” visitors. The method must have one parameter declared as the visitor interface type.
  4. Concrete elements must implement the receive method. The purpose of this method is to redirect its call to the method of the appropriate visitor based on the current element class. Note that even if the element base class implements the method, all subclasses must override it and call the appropriate methods in the visitor object.
  5. A Client is typically a representation of a collection or other complex object, such as a combination tree. Clients often do not know all of the concrete element classes because they interact with the objects in the collection through abstract interfaces.

10.4. Advantages and disadvantages

  1. Open and close principle. You can introduce new behaviors that perform on different classes of objects without making changes to those classes.
  2. Single responsibility principle. You can move different versions of the same behavior into the same class.
  3. Visitor objects can gather useful information as they interact with various objects. This information can be useful when you want to traverse some complex object structure (such as an object tree) and apply visitors to each object in the structure.
  • Each time you add or remove a class from the element hierarchy, you update all visitors.
  • When visitors interact with an element, they may not have the necessary permissions to access the element’s private member variables and methods.

10.5. Relationship with other modes

  1. You can think of the visitor pattern as an enhanced version of the command pattern, where objects can perform operations on multiple objects of different classes.

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

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

The path of The Java Engineer Design pattern (1) The path of the Java Engineer design pattern (2) the path of the Java engineer design pattern (3)