Relearn the behavioral pattern of design patterns

Relearn the behavioral pattern of design patterns

The behavior model is responsible for efficient communication and delegation of responsibilities between objects.

Chain of Responsibility Model

The chain of responsibility model allows you to send requests along the processor chain. After receiving the request, each processor can process the request or pass it to the next processor on the chain.

Applicable scene

  1. When different types of requests need to be processed in different ways, and the request type and order are unknown in advance, the chain of responsibility model can be used. This mode can connect multiple processors into a chain. After receiving the request, it will "ask" each processor if it can handle it. This way all processors have the opportunity to process the request.
  2. This mode can be used when multiple processors must be executed in sequence.
  3. If the required processors and their order must be changed at runtime, the chain of responsibility model 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.

Method to realize

  1. Declare the processor interface and describe the processing method. Determine how the client will pass the request data to the method. The most flexible way is to convert the request into an object, and then pass it to the processing function as a parameter.
  2. Create an abstract processing base class based on the processor interface to eliminate duplication of code. This class needs a member variable to store a reference to the next processor in the chain.
  3. In turn, create specific handler subclasses and implement their processing methods. Each processor must make two decisions after receiving the request:
    • Whether to handle this request yourself.
    • Whether to pass the request along the chain.
  4. Assemble the chain yourself, or obtain a pre-assembled chain from another object. In the latter case, the factory class must be implemented to create a chain based on configuration or environmental settings.
  5. The client can trigger any processor in the chain, not just the first one. The request will be passed through the chain until a processor refuses to continue or the request reaches the end of the chain.
  6. Due to the dynamic nature of the chain, you need to be prepared to deal with 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.

Code

Specific business: The application for employee reimbursement needs to be approved by the leader, but due to the issue of the amount of approval, it needs the leader of the corresponding level to approve.

public abstract class Leader { //The upper level leader protected Leader nexHandler; public final void handleRequest ( int money) { if (money <limit()) { handle(money); } else { if ( null != nexHandler) { nexHandler.handleRequest(money); } } } /** * The amount of reimbursement approved by the current level of leadership * * @return quota */ public abstract int limit () ; /** * Processing quota approval * * @param money specific amount */ public abstract void handle ( int money) ; } public class GroupLeader extends Leader { @Override public int limit () { return 1000 ; } @Override public void handle ( int money) { System.out.println( "Team leader approves reimbursement" +money+ "yuan" ); } } public class Director extends Leader { @Override public int limit () { return 5000 ; } @Override public void handle ( int money) { System.out.println( "Supervisor Approval and Reimbursement" + money + "yuan" ); } } public class Manager extends Leader { @Override public int limit () { return 10000 ; } @Override public void handle ( int money) { System.out.println( "Manager Approval and Reimbursement" + money + "yuan" ); } } public class Boos extends Leader { @Override public int limit () { return Integer.MAX_VALUE; } @Override public void handle ( int money) { System.out.println( "Labor Insurance Approval and Reimbursement" + money + "yuan" ); } } public class DesignPatternsTest { @Test public void chainTest () { //Create each node in the responsibility chain GroupLeader groupLeader = new GroupLeader(); Director director = new Director(); Manager manager = new Manager(); Boos boos = new Boos(); //specify the next node of the node groupLeader.nexHandler = director; director.nexHandler = manager; manager.nexHandler = boos; //Determine the processing node based on the amount groupLeader.handleRequest( 300 ); groupLeader.handleRequest( 4000 ); groupLeader.handleRequest( 6000 ); groupLeader.handleRequest( 100000 ); } } The team leader approves the reimbursement of 300 yuan The supervisor approves the reimbursement of 4000 yuan The manager approves and reimburses 6000 yuan Labor approved the reimbursement 100000 Yuan copy the code

Command mode

Command mode can convert the request into an independent object that contains all the information related to the request. This conversion allows you to parameterize methods, delay request execution or put them in the queue according to different requests, and implement undoable operations.

Applicable scene

  1. Objects need to be parameterized through operations. Command mode can convert specific method calls into independent objects. This change also brings many interesting applications: you can pass commands as method parameters, save commands in other objects, or switch connected commands at runtime.
  2. Need to put the operation in the queue, perform the operation or execute the operation remotely.
  3. Need to support operation rollback.

Method to realize

  1. Declare that there is only one command interface for execution methods.
  2. Extract the request and make it a specific command class that implements the command interface. Each class must have a set of member variables to 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 is responsible for the sender. Add member variables to save commands in these classes. The sender can only interact with its commands through the command interface. The sender usually does not create the command object itself, but obtains it through client code.
  4. Modify the sender to execute the command instead of sending the request directly to the receiver.
  5. The client must initialize the object in the following order:
    • Create recipients.
    • Create a command and associate it with the recipient if necessary.
    • Create a sender and associate it with a specific command.

Code

Specific business: simple operation of simulating Tetris game

//Recipient role Tetris game machine public class TetrisMachine { /** * The logic code that actually handles the "left" operation */ public void toLeft () { System.out.println( "Left" ); } /** * The logic code that actually handles the "rightward" operation */ public void toRight () { System.out.println( "to the right" ); } /** * The logic code that really handles the "speeding down" operation */ public void fastToBottom () { System.out.println( "Accelerate Down" ); } /** * The logic code that actually handles the "deformation" operation */ public void transform () { System.out.println( "Deformation" ); } } //Command abstract definition execution interface public interface Command { void execute () ; } public class LeftCommand implements Command { private TetrisMachine mMachine; public LeftCommand (TetrisMachine machine) { mMachine = machine; } @Override public void execute () { mMachine.toLeft(); } } public class RightCommand implements Command { private TetrisMachine mMachine; public RightCommand (TetrisMachine machine) { mMachine = machine; } @Override public void execute () { mMachine.toRight(); } } public class FallCommand implements Command { private TetrisMachine mMachine; public FallCommand (TetrisMachine machine) { mMachine = machine; } @Override public void execute () { mMachine.fastToBottom(); } } public class TransformCommand implements Command { private TetrisMachine mMachine; public TransformCommand (TetrisMachine machine) { mMachine = machine; } @Override public void execute () { mMachine.transform(); } } public class Buttons { private LeftCommand mLeftCommand; private RightCommand mRightCommand; private FallCommand mFallCommand; private TransformCommand mTransformCommand; /** * Set the command object to move left * * @param leftCommand The command object that moves to the left */ public void setLeftCommand (LeftCommand leftCommand) { mLeftCommand = leftCommand; } /** * Set the right movement command object * * @param rightCommand The command object that moves to the right */ public void setRightCommand (RightCommand rightCommand) { mRightCommand = rightCommand; } /** * Set the command object for fast falling * * @param fallCommand The command object for fast falling and moving */ public void setFallCommand (FallCommand fallCommand) { mFallCommand = fallCommand; } /** * Set the deformed command object * * @param transformCommand deformed command object */ public void setTransformCommand (TransformCommand transformCommand) { mTransformCommand = transformCommand; } /** * Press the button to the left */ public void toLeft () { mLeftCommand.execute(); } /** * Press the button to the right */ public void toRight () { mLeftCommand.execute(); } /** * Press the button to fall quickly */ public void fall () { mFallCommand.execute(); } /** * Press the button to change the shape */ public void transform () { mTransformCommand.execute(); } } public class DesignPatternsTest { @Test public void coomandTest () { //Command the receiver to create TetrisMachine tetrisMachine = new TetrisMachine(); //Construct four commands LeftCommand leftCommand = new LeftCommand(tetrisMachine); RightCommand rightCommand = new RightCommand(tetrisMachine); FallCommand fallCommand = new FallCommand(tetrisMachine); TransformCommand transformCommand = new TransformCommand(tetrisMachine); //Buttons execute different commands Buttons buttons = new Buttons(); buttons.setLeftCommand(leftCommand); buttons.setRightCommand(rightCommand); buttons.setFallCommand(fallCommand); buttons.setTransformCommand(transformCommand); //Press the corresponding key buttons.toLeft(); buttons.toRight(); buttons.transform(); buttons.fall(); } } left left Deformed Accelerate downward Copy code

Iterator mode

The iterator pattern can traverse all the elements in the collection without exposing the underlying representation of the collection (list, stack, tree, etc.).

Applicable scene

  1. When there is a complex data structure behind the collection and you want to hide its complexity from the client (for convenience or safety considerations), you can use the iterator mode. Iterators encapsulate the details of interacting 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 prevents the client from performing wrong or harmful operations when directly interacting with the collection, thereby protecting the collection.
  2. Using this mode can reduce the repetitive traversal code in the program.
  3. If you want your code to be able to traverse different or even unpredictable data structures, you can use the iterator pattern.

Method to realize

  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 also add some other methods, such as getting the previous element, recording the current position, and judging whether the iteration has ended.
  2. Declare the collection interface and describe a method for obtaining iterators. The return value must be an iterator interface. If you plan to have multiple sets of different iterators, you can declare multiple similar methods.
  3. Implement specific iterator classes for collections that you want to traverse using iterators. The iterator object must be linked with a single collection entity, and the link relationship is usually established through the iterator's constructor.
  4. Implement the collection interface in the collection class. The main idea is to provide client code with a shortcut to create iterators for a specific collection. The collection object must pass itself to the iterator's constructor to create a link between the two.
  5. Check the client code and use iterators to replace all collection traversal code. Whenever the client needs to traverse the collection elements, it will get a new iterator.

Code

//Iterator interface public interface Iterator < T > { /** * Is there a next element * @return boolean */ boolean haseNext () ; /** * The next element at the current element position * @return T */ T next () ; } //Concrete Iterator class public class ConcreteIterator < T > implements Iterator < T > { private List<T> list = new ArrayList<>(); private int cursor = 0 ; public ConcreteIterator (List<T> list) { this .list = list; } @Override public boolean haseNext () { return cursor != list.size(); } @Override public T next () { T obj = null ; if ( this .haseNext()) { obj = this .list.get(cursor++); } return obj; } } //Container interface public interface Aggregate < T > { /** * Add an element * @param obj element */ void add (T obj) ; /** * Delete an element * @param obj element */ void remove (T obj) ; /** * Get container iterator * @return iterator */ Iterator<T> iterator () ; } //Concrete container public class ConcreteAggregate < T > implements Aggregate < T > { private List<T> list = new ArrayList<>(); @Override public void add (T obj) { list.add(obj); } @Override public void remove (T obj) { list.remove(obj); } @Override public Iterator<T> iterator () { return new ConcreteIterator<>(list); } } public class DesignPatternsTest { @Test public void chainTest () { Aggregate<String> aggregate = new ConcreteAggregate<>(); aggregate.add( "Liu Bei" ); aggregate.add( "guan Yu" ); aggregate.add( "Zhang Fei" ); aggregate.add( "Huang Zhong" ); Iterator<String> iterator = aggregate.iterator(); while (iterator.haseNext()){ System.out.println(iterator.next()); } } } Liu Bei Guan Yu Zhang Fei Huang Zhong Copy code

Intermediary model

The intermediary model can reduce the disorderly dependency between objects. This mode restricts direct interaction between objects, forcing them to cooperate through an intermediary object.

Applicable scene

  1. When some objects are so tightly coupled with other objects that it is difficult to modify them, the mediator pattern can be used. This mode allows you to extract all the relationships between objects into a separate class, so that the modification of a specific component is independent of other components.
  2. When the component is too dependent on other components and cannot be reused in different applications, the mediator pattern can be used. After applying the mediator pattern, each component no longer knows the status of other components. Although these components cannot communicate directly, they can still communicate indirectly through intermediary objects. If you want to reuse a component in different applications, you need to provide a new mediator class for it.
  3. If you need to be forced to create a large number of component subclasses in order to be able 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, it is easy to create a new mediator class to define a new way of component cooperation without modifying the components.

Method to realize

  1. Find a group of classes that are currently tightly coupled and provide greater benefits for their independent performance (for example, easier to maintain or easier to reuse).
  2. Declare the intermediary interface and describe the required communication interface between the intermediary and various components. In most cases, a method of receiving component notifications is sufficient. If you want to reuse component classes in different scenarios, then this interface will be very important. As long as the component uses a common interface to cooperate with the intermediary, you can connect the component with the intermediary in different implementations.
  3. Implement concrete intermediary classes. This class can benefit from keeping the references of all the components under it.
  4. Let the intermediary be responsible for the creation and destruction of component objects. Thereafter, the intermediary may resemble the factory or appearance.
  5. The component must hold a reference to the mediator object. The connection is usually established in the component's constructor, which will pass the mediator object as a parameter.
  6. Modify the component code so that it can call the notification method of the mediator instead of the method of other components. Then extract the code that calls other components into the mediator class, and execute these codes when the mediator receives the notification of the component.

##Code to achieve specific business: the computer motherboard coordinates the computer brother members.

//Abstract members public abstract class Colleague { //Every member knows the protected Mediator mediator; public Colleague (Mediator mediator) { this .mediator = mediator; } } // public abstract class Mediator { /** * The method of notifying the intermediary of the member object change * When a member changes, the intermediary informs other members * @param colleague member */ public abstract void changed (Colleague colleague) ; } //Member part Cpu public class Cpu extends Colleague { //Audio and video data private String videoData, soundData; public Cpu (Mediator mediator) { super (mediator); } public String geVideoData () { return videoData; } public String getSoundData () { return soundData; } public void decodeData (String data) { //Split audio and video data String[] temp = data.split( "," ); videoData = temp[ 0 ]; soundData = temp[ 1 ]; mediator.changed( this ); } } //member parts CD-ROM public class CdDevice extends Colleague { //video data private String data; public CdDevice (Mediator mediator) { super (mediator); } public String read () { return data; } public void load () { data = "Video data, audio data" ; //Notify the mediator that the data has changed mediator.changed( this ); } } //member part graphics card public class GraphicsCard extends Colleague { public GraphicsCard (Mediator mediator) { super (mediator); } public void playVideo (String data) { System.out.println( "Video:" + data); } } //member parts sound card public class SoundCard extends Colleague { public SoundCard (Mediator mediator) { super (mediator); } public void playVoice (String data) { System.out.println( "Audio:" +data); } } //Mainboard public class MainBoard extends Mediator { private CdDevice mCdDevice; private Cpu mCpu; private SoundCard mSoundCard; private GraphicsCard mGraphicsCard; @Override public void changed (Colleague colleague) { if (colleague instanceof CdDevice){ handleCd((CdDevice) colleague); } if (colleague instanceof Cpu){ handleCpu((Cpu) colleague); } } /** * Interact with other devices after processing the optical drive to read the data * @param cdDevice Irrigation area equipment */ private void handleCd (CdDevice cdDevice) { mCpu.decodeData(cdDevice.read()); } /** * Process the CPU to interact with other devices after reading the data * @param cpu cpu */ private void handleCpu (Cpu cpu) { mSoundCard.playVoice(cpu.getSoundData()); mGraphicsCard.playVideo(cpu.geVideoData()); } public void setCdDevice (CdDevice cdDevice) { mCdDevice = cdDevice; } public void setCpu (Cpu cpu) { mCpu = cpu; } public void setSoundCard (SoundCard soundCard) { mSoundCard = soundCard; } public void setGraphicsCard (GraphicsCard graphicsCard) { mGraphicsCard = graphicsCard; } } public class DesignPatternsTest { @Test public void mediatorTest () { //Construct the intermediary mainboard MainBoard mainBoard = new MainBoard(); //Construct each part Cpu cpu = new Cpu(mainBoard); CdDevice cdDevice = new CdDevice(mainBoard); GraphicsCard graphicsCard = new GraphicsCard(mainBoard); SoundCard soundCard = new SoundCard(mainBoard); //install each part to the motherboard mainBoard.setCdDevice(cdDevice); mainBoard.setCpu(cpu); mainBoard.setGraphicsCard(graphicsCard); mainBoard.setSoundCard(soundCard); //Play the video after the preparation is complete cdDevice.load(); } } Audio: Audio data Video: Video data Copy code

Memo mode

The memo mode allows to save and restore the previous state of the object without exposing the implementation details of the object.

Applicable scene

  1. When you need to create an object state snapshot to restore its previous state, you can use the memo mode. The memo mode allows you to copy all the state in the object (including private member variables) and save it 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 processing transactions (such as the need to roll back an operation when an error occurs).
  2. This mode can be used when direct access to the member variables, getters, or setters of the object will cause the encapsulation to be broken. The memo puts the subject in charge of creating a snapshot of its state. No other objects can read the snapshot, which effectively guarantees the security of the data.

Method to realize

  1. Determine the class that plays the role of the originator. It is important to clarify whether the central object of the originator used by the program or multiple smaller objects.
  2. Create a memo class. Declare the memo member variables corresponding to each originator member variable one by one.
  3. Set the memo class to immutable. The memo can only receive data once through the constructor. Setters cannot be included in this class.
  4. If the programming language used supports nested classes, you can nest the memo in the originator; if not, you can extract an empty interface from the memo class, and then let all other objects refer to the memo through the interface. Some metadata operations can be added to this interface, but the state of the originator cannot be exposed.
  5. Add a method to create a memo in the originator. The originator must pass its state to the memo through one or more actual parameters of the memo constructor.
  6. Add a method to restore its own state in the originator class. This method accepts the memo object as a parameter. If you extracted the interface in the previous step, you can use the interface as the type of the parameter. In this case, the input object needs to be forced into a memo because the originator needs to have full access to the object.
  7. Regardless of whether the person in charge is a command object, history or other completely different things, it must know when to request a new memo from the originator, how to store the memo, and when to use a specific memo to restore the originator.
  8. The connection between the person in charge and the originator can be moved to the memo class. In this example, each memo must be connected to the originator that created its own. The recovery method can also be moved to the memo class, but this method can only be implemented when the memo class is nested in the originator, or the originator class provides enough setters and can override its state .

Code

//data model public class CallOfDuty { private int mCheckPoint = 1 ; private int mLifeValue = 100 ; private String mWeapon = "Desert Eagle" ; public void play () { System.out.println( "Playing games:" + String.format( "%s off" , mCheckPoint) + "Fighting to kill the enemy" ); mLifeValue -= 10 ; System.out.println( "Game Progress Upgrade" ); mCheckPoint++; System.out.println( "Arrival" + String.format( "%s off" , mCheckPoint)); } public void quit () { System.out.println( "----------" ); System.out.println( "Properties before exiting the game:" + this .toString()); System.out.println( "Exit the game" ); System.out.println( "----------" ); } public Remark createRemark () { Remark remark = new Remark(); remark.mCheckPoint = mCheckPoint; remark.mLifeValue = mLifeValue; remark.mWeapon = mWeapon; return remark; } public void restore (Remark remark) { this .mCheckPoint = remark.mCheckPoint; this .mLifeValue = remark.mLifeValue; this .mWeapon = remark.mWeapon; System.out.println( "Game attributes after restoration:" + this .toString()); } @Override public String toString () { return "CallOfDuty{" + "mCheckPoint=" + mCheckPoint + ", mLifeValue=" + mLifeValue + ", mWeapon='" + mWeapon + '\'' + '}' ; } } //Memo class public class Remark { public int mCheckPoint = 1 ; public int mLifeValue = 100 ; public String mWeapon = "Desert Eagle" ; @Override public String toString () { return "Remark{" + "mCheckPoint=" + mCheckPoint + ", mLifeValue=" + mLifeValue + ", mWeapon='" + mWeapon + '\'' + '}' ; } } //Responsible for managing Remark public class Caretaker { Remark mRemark; /** * Archive * @param remark memo */ public void archive (Remark remark) { this .mRemark = remark; } /** * Get archive * @return archive */ public Remark getRemark () { return mRemark; } } public class DesignPatternsTest { @Test public void remarkTest () { //Build a game object CallOfDuty game = new CallOfDuty(); game.play(); //Archive Caretaker caretaker = new Caretaker(); caretaker.archive(game.createRemark()); //exit the game game.quit(); //Resuming the game CallOfDuty newGame = new CallOfDuty(); newGame.restore(caretaker.getRemark()); } } Play the game: Fight and kill the enemy in level 1 Game progress upgrade Reach the first 2 off ---------- Attributes before exiting the game: CallOfDuty{mCheckPoint= 2 , mLifeValue= 90 , mWeapon= 'Desert Eagle' } exit the game ---------- After recovery properties of the game: CallOfDuty mCheckPoint = { 2 , mLifeValue = 90 , mWeapon = 'Shamozhiying' } copy the code

Observer mode

The observer pattern defines a subscription mechanism that can notify multiple other objects that "observe" the object when an object event occurs.

Applicable scene

  1. When an object's state changes need to be notified to other objects, or the actual object is unknown in advance or changes dynamically, the observer mode can be used.
  2. This mode can be used when some objects in the application must observe other objects. But it can only be used for a limited time or under certain circumstances.

Method to realize

  1. Declare the subscriber interface, the interface declares at least one
    update()
    Method, the publisher calls the subscriber s
    update()
    Method to notify updates.
  2. Declare the publisher interface and define some interfaces to add and delete subscription objects in the subscriber list. Publishers must interact with them only through the subscriber interface.
  3. Determine where to store the actual subscription list and implement the subscription method. Usually all types of publisher code look the same, so it is obvious to place the list in an abstract class that directly extends the publisher interface. The specific publisher will extend this class to inherit all subscription behaviors. However, if you need to apply this pattern in the existing class hierarchy, you can consider using a combination method: put the subscription logic into a separate object, and then let All actual subscribers use this object.
  4. Create a specific publisher class. Every time an important event occurs to the publisher, all subscribers must be notified.
  5. Implement the notification update method in the specific subscriber class. Most subscribers need some contextual data related to the event. These data can be passed as parameters of the notification method. But there is another option. After receiving the notification, the subscriber obtains all data directly from the notification. In this case, the publisher must pass itself through the update method. Another less flexible way is to permanently connect the publisher and the subscriber through the constructor.
  6. The client must generate all required subscribers and complete the registration work at the corresponding publisher.

Code

//Subscriber interface public interface Observer { void update (Object arg) ; } //Publisher public class Observable { private List<Observer> mObserverList = new ArrayList<>(); public void addObserver (Observer observer) { mObserverList.add(observer); } public void removeObserver (Observer observer) { mObserverList.remove(observer); } public void notifyObservers (Object arg) { for (Observer observer: mObserverList) { observer.update(arg); } } } //Specific subscribers public class Coder implements Observer { public String name; public Coder (String name) { this .name = name; } @Override public void update (Object arg) { System.out.println( "Hi " + name + ", AndroidDevelopers update content:" + arg); } @Override public String toString () { return "Coder{" + "name='" + name + '\'' + '}' ; } } //The specific publisher public class AndroidDevelopers extends Observable { public void postNewContent (String content) { notifyObservers(content); } } public class DesignPatternsTest { @Test public void observerTest () { Coder coder1 = new Coder( " " ); Coder coder2 = new Coder( " " ); Coder coder3 = new Coder( "Bai Juyi" ); Coder coder4 = new Coder( "Li Qingzhao" ); Coder coder5 = new Coder( "Wen Tingyun" ); Coder coder6 = new Coder( " " ); AndroidDevelopers androidDevelopers = new AndroidDevelopers(); androidDevelopers.addObserver(coder1); androidDevelopers.addObserver(coder2); androidDevelopers.addObserver(coder3); androidDevelopers.addObserver(coder4); androidDevelopers.addObserver(coder5); androidDevelopers.addObserver(coder6); androidDevelopers.postNewContent( "Compose start" ); } } Hi Li Bai, Android Developers Update: Compose starts Hi Du Fu, AndroidDevelopers Update: Compose starts Hi Bai Juyi, Android Developers update content: Compose starts Hi Li Qingzhao, Android Developers update content: Compose starts Hi Wen Tingyun, Android Developers update content: Compose starts Hi Wen Tianxiang, AndroidDevelopers Update: Compose starts Copy code

State mode

State mode can change the behavior of an object when its internal state changes, making it look like it has changed the class to which it belongs. The specific behavior is encapsulated in the state, and the specific behavior realizes different functions according to its state.

Applicable scene

  1. If the object needs to behave differently according to its current state, and the number of states is very large and the code related to the state will frequently change, the state mode can be used.
  2. If a class needs to change its behavior based on the current value of a member variable, which requires a large number of conditional statements, use this mode. The state mode 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 codes related to a specific state in the main class.
  3. State mode can be used when there are many repetitive codes in similar states and condition-based state machine transitions.

Method to realize

  1. Determine which class is the context. It may be an existing class that contains state-dependent code; if state-specific code is scattered across multiple classes, it may be a new class.
  2. Declare the state interface. Although it may be necessary to fully replicate 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 that inherits from the state interface for each actual state. Then check the methods in the context and extract all the code related to the specific state into the newly created class. In the process of moving the code to the state class, you may find that it depends on some private members in the context. For this situation, the following workarounds can be used:
    • Make these member variables or methods public.
    • Change the context behavior that needs to be extracted to the public method in the context, and then call it in the state class. This method is simple but convenient and can be patched later.
    • Nest the state class in the context class. This method requires the programming language you are using to support nested classes.
  4. Add a reference member variable of the state interface type and a public setter for modifying the value of the member variable in the context class.
  5. Check the method in the context again, and replace the empty conditional statement with the corresponding state object method.
  6. To switch the context state, you need to create an instance of a state class and pass it to the context. This can be done in the context, various states, or the client. No matter where the work is done, the class will depend on the concrete class it instantiates.

Code

Take TV remote control as an example

//TV interface, defines the TV operation behavior function public interface TvState { void nextChannel () ; void prevChannel () ; void turnUp () ; void turnDown () ; } //TV off state public class PowerOffState implements TvState { @Override public void nextChannel () { System.out.println( "The next channel operation is invalid\n" ); } @Override public void prevChannel () { System.out.println( "The previous channel operation is invalid\n" ); } @Override public void turnUp () { System.out.println( "The operation of turning up the volume in the shutdown state is invalid\n" ); } @Override public void turnDown () { System.out.println( "The operation of turning down the volume in the shutdown state is invalid\n" ); } } //TV off state public class PowerOnState implements TvState { @Override public void nextChannel () { System.out.println( "Next Channel\n" ); } @Override public void prevChannel () { System.out.println( "Previous Channel\n" ); } @Override public void turnUp () { System.out.println( "Turn up the volume\n" ); } @Override public void turnDown () { System.out.println( "Turn down the volume\n" ); } } //Power operation interface public interface PowerController { void powerOn () ; void powerOff () ; } public class TvController implements PowerController { TvState mTvState; public void setTvState (TvState tvState) { mTvState = tvState; } @Override public void powerOn () { setTvState( new PowerOnState()); System.out.println( "Power on\n" ); } @Override public void powerOff () { setTvState( new PowerOffState()); System.out.println( "Shut down\n" ); } public void nextChannel () { mTvState.nextChannel(); } public void prevChannel () { mTvState.prevChannel(); } public void turnUp () { mTvState.turnUp(); } public void turnDown () { mTvState.turnDown(); } } Power on Next channel Previous channel Turn down the volume Turn up the volume Shut down Turning off the volume is invalid Copy code

Strategy mode

The strategy mode defines a series of algorithms and puts each algorithm into a separate class, so that the objects of the algorithm can replace each other.

Applicable scene

  1. When using various algorithm variants in the object and wishing to switch the algorithm at runtime, you can use the strategy mode. The strategy pattern can associate objects with different sub-objects that can perform specific sub-tasks in different ways, thereby indirectly changing the behavior of the object at runtime.
  2. The strategy pattern can be used when there are many similar classes that differ only slightly when performing certain actions. The strategy pattern can extract different behaviors into an independent class hierarchy, and combine the original classes into one, thereby reducing duplication of code.
  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 implementation details of the algorithm.
  4. This mode can be used when complex conditional operators are used in a class to switch between different variants of the same algorithm.

Method to realize

  1. Find the algorithm with a higher frequency of modification from the context class (may also be a complex conditional operator used to select an algorithm variant at runtime).
  2. Declare a common policy interface for all variants of the algorithm.
  3. To extract the algorithms one by one into their respective classes, they must all implement the strategy interface.
  4. Add a member variable in the context class to save the reference to the policy object. Then provide a setter to modify the member variable. The context can only interact with the policy object through the policy interface. If necessary, an interface can be defined to allow the policy to access its data.
  5. The client must associate the context class with the corresponding strategy so that the context can complete its main work in an expected manner.

Code

public interface CalculateStrategy { int calculatePrice ( int km) ; } public class SubwayStrategy implements CalculateStrategy { @Override public int calculatePrice ( int km) { if (km < 6 ) { return 3 ; } else if (km> 6 && km < 12 ) { return 4 ; } else if (km> 12 && km < 22 ) { return 5 ; } else if (km> 22 && km < 32 ) { return 6 ; } return 7 ; } } public class BusStrategy implements CalculateStrategy { @Override public int calculatePrice ( int km) { //total endorsement for more than 10 kilometers int extraTotal = km- 10 ; //multiples of more than 5 kilometers int extraFactor = extraTotal/5 ; //distance exceeded Take the remainder of 5 int fraction = extraTotal% 5 ; //add a calculation int price = 1 + extraFactor; return fraction> 0 ? ++price: price; } } public class TranficCalculator { private CalculateStrategy mStrategy; public TranficCalculator () { } public void setStrategy (CalculateStrategy strategy) { mStrategy = strategy; } public int calculatePrice ( int km) { return mStrategy.calculatePrice(km); } } public class DesignPatternsTest { @Test public void strategyTest () { TranficCalculator tranficCalculator = new TranficCalculator(); tranficCalculator.setStrategy( new SubwayStrategy()); int subwayPrice = tranficCalculator.calculatePrice( 10 ); System.out.println( "10km subway trip price" + subwayPrice + "yuan" ); tranficCalculator.setStrategy( new BusStrategy()); int busPrice = tranficCalculator.calculatePrice( 10 ); System.out.println( "Metro 10km travel price" + busPrice + "yuan" ); } } The price of 10 kilometers by subway is 4 yuan Metro 10 Gongli travel price 1 Yuan Copy the code

Template method mode

The template method pattern defines an algorithm framework in the superclass, allowing subclasses to rewrite specific steps of the algorithm without modifying the structure.

Applicable scene

  1. When you only want the client to extend a specific algorithm step instead of the entire algorithm or its structure, you can use the template method mode.
  2. This mode can be used when the algorithms of multiple classes are almost identical except for some slight differences. But the consequence is that as long as the algorithm changes, all classes may need to be modified.

Method to realize

  1. Analyze the target algorithm and determine whether it can be broken down into multiple steps. From the perspective of all sub-categories, consider which steps are common and which steps 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. In the template method, the corresponding steps are called in sequence according to the algorithm structure. You can use final to modify the template method to prevent subclasses from overriding it.
  3. Although all steps can be set as abstract types, the default implementation may bring benefits to some steps, because subclasses do not need to implement those methods.
  4. Consider adding hooks between the key steps of the algorithm.
  5. Create a new concrete subclass for each algorithm variant. It must implement all the abstract steps, and it can also rewrite some optional steps.

Code

//Abstract Computer public abstract class AbstractComputer { protected void powerOn () { System.out.println( "Turn on the power" ); } protected void checkHardware () { System.out.println( "Hardware Check" ); } protected void loadOs () { System.out.println( "Enter the operating system" ); } protected void login () { System.out.println( "Xiaobai's computer has no verification, directly enter the system" ); } protected void startUp () { System.out.println( "------------ Boot ------------" ); powerOn(); checkHardware(); loadOs(); login(); System.out.println( "------------ Shutdown------------" ); } } //Programmer s computer public class CoderComputer extends AbstractComputer { @Override protected void login () { System.out.println( "Programmers only need to verify the user and password to start the computer" ); } } //military computer public class MilitaryComputer extends AbstractComputer { @Override protected void checkHardware () { super .checkHardware(); System.out.println( "See Harbin Hardware Firewall" ); } @Override protected void login () { System.out.println( "Perform complex user verification such as fingerprint recognition" ); } } public class DesignPatternsTest { @Test public void test () { CoderComputer coderComputer = new CoderComputer(); coderComputer.startUp(); MilitaryComputer militaryComputer = new MilitaryComputer(); militaryComputer.startUp(); } } ------------ Boot ------------ Power on Hardware check Enter the operating system The programmer only needs to verify the user and password to start the computer ------------ Shutdown------------ ------------ Boot ------------ Power on Hardware check See Kazakhstan hardware firewall Enter the operating system Perform complex user verification such as fingerprint recognition ------------ Shutdown------------ Copy code

Visitor mode

The visitor pattern can isolate the algorithm from the object it acts on.

scenes to be used

  1. If you need to perform certain operations on all elements in a complex object structure (such as an object tree), you can use the visitor pattern.
  2. The visitor pattern can be used to clean up the business logic of the auxiliary behavior.
  3. This mode can be used when a behavior is only meaningful in some classes in the class hierarchy, but not in other classes.

Method to realize

  1. Declare a group of "access" methods in the visitor interface, corresponding to each specific element class in the program.
  2. Declare the element interface. If the program has an element class hierarchical interface, you can add an abstract "receive" method to the base class of the hierarchy. The method must accept the visitor object as a parameter.
  3. Implement the receiving method 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 specific element classes, because these classes are referenced as parameter types in the visitor method.
  5. Create a specific visitor class and implement all visitor methods for each behavior that cannot be implemented in the element hierarchy.
  6. The client must create the visitor object and pass it to the element through the "receive" method.

Code

public abstract class Staff { public String name; public int kpi; public Staff (String name) { this .name = name; this .kpi = new Random().nextInt(); } public abstract void accept (Visitor visitor) ; } public class Engineer extends Staff { public Engineer (String name) { super (name); } @Override public void accept (Visitor visitor) { visitor.visit( this ); } public int getCodeLines () { return new Random().nextInt( 10 * 1000 ); } } public class Manager extends Staff { private int products; public Manager (String name) { super (name); products = new Random().nextInt( 10 ); } @Override public void accept (Visitor visitor) { visitor.visit( this ); } public int getProducts () { return products; } } public interface Visitor { void visit (Engineer engineer) ; void visit (Manager manager) ; } public class CtoVisitor implements Visitor { @Override public void visit (Engineer engineer) { System.out.println( "Engineer:" + engineer.name + ", the number of code lines:" + engineer.getCodeLines()); } @Override public void visit (Manager manager) { System.out.println( "Manager:" + manager.name + ",kpi:" + manager.kpi+ ", the number of new products: " +manager.getProducts()); } } public class CeoVisitor implements Visitor { @Override public void visit (Engineer engineer) { System.out.println( "Engineer:" + engineer.name + ",kpi:" + engineer.kpi); } @Override public void visit (Manager manager) { System.out.println( "Manager:" + manager.name + ",kpi:" + manager.kpi+ ", the number of new products:" +manager.getProducts()); } } public class BusinessReport { List<Staff> mStaffs = new ArrayList<>(); public BusinessReport () { mStaffs.add( new Manager( " " )); mStaffs.add( new Manager( "Cui Manager " )); mStaffs.add( new Engineer( "Engineer-Liu Bei" )); mStaffs.add( new Engineer( "Engineer-Guan Yu" )); mStaffs.add( new Engineer( "Engineer-Zhang Fei" )); } public void showReport (Visitor visitor) { for (Staff staff: mStaffs) { staff.accept(visitor); } } } public class DesignPatternsTest { @Test public void visitorTest () { BusinessReport businessReport = new BusinessReport(); System.out.println( "---------- Report to the CEO----------" ); businessReport.showReport( new CeoVisitor()); System.out.println( "---------- Report to CTO----------" ); businessReport.showReport( new CtoVisitor()); } } ---------- Report for the CEO ---------- Manager: Manager Wang, kpi: 1098834980 , number of new products: 5 Manager: Manager Cui, kpi: -1039068197 , number of new products: 3 Engineer: Engineer-Liu Bei, kpi: -507271997 Engineer: Engineer-Guan Yu, kpi: 808473843 Engineer: Engineer-Zhang Fei, kpi: -231964394 ---------- Report for CTO ---------- Manager: Manager Wang, kpi: 1098834980 , Number of new products: 5 Manager: Manager Cui, Kpi: -1039068197 , Number of new products: 3 Engineer: Engineer-Liu Bei, Number of lines of code: 6496 Engineer: Engineer-Guan Yu, Number of lines of code: 2636 engineers: engineers - Zhang Fei, a few lines of code: 8813 copy the code