Behavioral patterns are responsible for effective communication and delegation of responsibilities between objects.
Chain of Responsibility model
The chain of responsibility pattern allows you to send requests down the 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.
Applicable scenario
- The chain of responsibility pattern can be used when different types of requests need to be handled 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.
- This pattern can be used when multiple handlers must be executed in sequence.
- 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.
implementation
- Declare the handler interface and describe the processing methods. 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.
- Create an abstract processing base class based on the handler interface to eliminate duplicate code. This class requires a member variable to store a reference to the next handler in the chain.
- 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.
- Self-assemble chains, or obtain pre-assembled chains from other objects. In the latter case, the factory class must be implemented to create chains based on configuration or environment Settings.
- 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.
- Due to the dynamic nature of chains, you need to be prepared to handle the following:
- 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 implementation
Specific business: Employees’ expense reimbursement application needs the approval of leaders, but due to the problem of the approval amount, it needs the approval of leaders of corresponding levels.
public abstract class Leader {
// The leader at the next level
protected Leader nexHandler;
public final void handleRequest(int money) {
if (money < limit()) {
handle(money);
} else {
if (null! = nexHandler) { nexHandler.handleRequest(money); }}}/** * Reimbursement amount approved by current level leaders **@returnLines * /
public abstract int limit(a);
/** ** Processing quota approval **@paramMoney Specific amount */
public abstract void handle(int money);
}
public class GroupLeader extends Leader{
@Override
public int limit(a) {
return 1000;
}
@Override
public void handle(int money) {
System.out.println("Group leader approved reimbursement"+money+"Yuan"); }}public class Director extends Leader {
@Override
public int limit(a) {
return 5000;
}
@Override
public void handle(int money) {
System.out.println("Supervisor approves reimbursement." + money + "Yuan"); }}public class Manager extends Leader {
@Override
public int limit(a) {
return 10000;
}
@Override
public void handle(int money) {
System.out.println("Manager approves reimbursement" + money + "Yuan"); }}public class Boos extends Leader {
@Override
public int limit(a) {
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(a) {
// 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 according to the amount
groupLeader.handleRequest(300);
groupLeader.handleRequest(4000);
groupLeader.handleRequest(6000);
groupLeader.handleRequest(100000); }} The group leader approved the reimbursement300Yuan supervisor approved reimbursement4000Manager Yuan approved the reimbursement6000Yuan labor insurance approval reimbursement100000yuanCopy the code
Command mode
Command pattern It transforms a request into a separate object that contains all the information related to the request. This transformation allows you to parameterize methods, delay request execution, or queue them based on different requests, and implement undoable operations.
Applicable scenario
- You need operations to parameterize objects. 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.
- Operations need to be queued, executed, or executed remotely.
- Operation rollback is required.
implementation
- Declare a single command interface that executes a method.
- 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.
- 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.
- Modify the sender to execute the command instead of sending the request directly to the receiver.
- 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.
Code implementation
Specific business: simple operation of the simulation of Tetris game
// Receiver character Tetris game
public class TetrisMachine {
/** * the logical code that actually handles the "left" operation */
public void toLeft(a) {
System.out.println("The left");
}
/** * the logical code that actually handles the "right" operation */
public void toRight(a) {
System.out.println("Right");
}
/** * the logical code that actually handles the "speed down" operation */
public void fastToBottom(a) {
System.out.println("Accelerate down");
}
/** * The logical code that actually handles the "morph" operation */
public void transform(a) {
System.out.println("Deformation"); }}// The command abstracts the execution interface
public interface Command {
void execute(a);
}
public class LeftCommand implements Command {
private TetrisMachine mMachine;
public LeftCommand(TetrisMachine machine) {
mMachine = machine;
}
@Override
public void execute(a) { mMachine.toLeft(); }}public class RightCommand implements Command {
private TetrisMachine mMachine;
public RightCommand(TetrisMachine machine) {
mMachine = machine;
}
@Override
public void execute(a) { mMachine.toRight(); }}public class FallCommand implements Command {
private TetrisMachine mMachine;
public FallCommand(TetrisMachine machine) {
mMachine = machine;
}
@Override
public void execute(a) { mMachine.fastToBottom(); }}public class TransformCommand implements Command {
private TetrisMachine mMachine;
public TransformCommand(TetrisMachine machine) {
mMachine = machine;
}
@Override
public void execute(a) { mMachine.transform(); }}public class Buttons {
private LeftCommand mLeftCommand;
private RightCommand mRightCommand;
private FallCommand mFallCommand;
private TransformCommand mTransformCommand;
/** * sets the move left command object **@paramLeftCommand Moves the command object */ to the left
public void setLeftCommand(LeftCommand leftCommand) {
mLeftCommand = leftCommand;
}
/** * sets the right move command object **@paramRightCommand moves the command object */ to the right
public void setRightCommand(RightCommand rightCommand) {
mRightCommand = rightCommand;
}
/** * sets the fast falling command object **@paramFallCommand Quickly drops the moving command object */
public void setFallCommand(FallCommand fallCommand) {
mFallCommand = fallCommand;
}
/** * set the command object ** to be deformed@paramTransformCommand Specifies the transformCommand object */
public void setTransformCommand(TransformCommand transformCommand) {
mTransformCommand = transformCommand;
}
/** * press the button left */
public void toLeft(a) {
mLeftCommand.execute();
}
/** ** press the button to the right */
public void toRight(a) {
mLeftCommand.execute();
}
/** * press the button to drop quickly */
public void fall(a) {
mFallCommand.execute();
}
/** * Press the button to change the shape */
public void transform(a) { mTransformCommand.execute(); }}public class DesignPatternsTest {
@Test
public void coomandTest(a) {
// Command the receiver to create the Tetris console
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);
// Button to execute different commands
Buttons buttons = new Buttons();
buttons.setLeftCommand(leftCommand);
buttons.setRightCommand(rightCommand);
buttons.setFallCommand(fallCommand);
buttons.setTransformCommand(transformCommand);
// Press the corresponding keybuttons.toLeft(); buttons.toRight(); buttons.transform(); buttons.fall(); }} Left to left deformation accelerated downCopy the code
Iterator pattern
The iterator pattern iterates through all the elements of a collection without exposing the underlying representation of the collection (lists, stacks, trees, etc.).
Applicable scenario
- 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). 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.
- Using this pattern can reduce repeated traversal code in the program.
- If you want your code to iterate over different or even unpredictable data structures, you can use the iterator pattern.
implementation
- 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.
- Declares the collection interface and describes a method for getting iterators. The return value must be an iterator interface. If your plan has multiple different sets of iterators, you can declare multiple similar methods.
- Implement concrete iterator classes for collections that you want to traverse using iterators. An iterator object must be linked to a single collection entity, usually through the iterator’s constructor.
- Implement the collection interface in the 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.
- 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.
Code implementation
// Iterator interface
public interface Iterator<T> {
/** * if there is a next element *@return boolean
*/
boolean haseNext(a);
/** * The element next to the current element *@return T
*/
T next(a);
}
// 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(a) {
returncursor ! = list.size(); }@Override
public T next(a) {
T obj = null;
if (this.haseNext()) {
obj = this.list.get(cursor++);
}
returnobj; }}// Container interface
public interface Aggregate<T> {
/** * add an element *@paramObj elements * /
void add(T obj);
/** * delete an element *@paramObj elements * /
void remove(T obj);
/** * get the container iterator *@returnThe iterator * /
Iterator<T> iterator(a);
}
// Specific 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(a) {
return newConcreteIterator<>(list); }}public class DesignPatternsTest {
@Test
public void chainTest(a) {
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 ZhongCopy the code
The mediator pattern
The mediator pattern reduces messy dependencies between objects. This pattern restricts direct interaction between objects, forcing them to collaborate through a mediator object.
Applicable scenario
- 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.
- 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.
- 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 inter-component relationships are contained within the mediator, it is easy to create a mediator class to define new ways of component cooperation without modifying the component.
implementation
- Find a set of classes that are currently tightly coupled and offer greater benefits (such as easier maintenance or reuse) from their independent performance.
- 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.
- Implement the concrete intermediary class. This class benefits from keeping references to all the components under it.
- Let the mediator be responsible for the creation and destruction of component objects. Thereafter, the intermediary may resemble a factory or look.
- 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.
- 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.
## Code to achieve specific business: computer motherboard coordination computer brother members.
// Abstract member
public abstract class Colleague {
// Every member knows the mediator
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator; }}// Abstract mediator
public abstract class Mediator {
/** * The method by which a member object notifies the mediator of a change in one member * the mediator notifies the other members *@paramMembers of the colleague * /
public abstract void changed(Colleague colleague);
}
// Member parts Cpu
public class Cpu extends Colleague {
// Audio and video data
private String videoData, soundData;
public Cpu(Mediator mediator) {
super(mediator);
}
public String geVideoData(a) {
return videoData;
}
public String getSoundData(a) {
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 drive
public class CdDevice extends Colleague{
// Video data
private String data;
public CdDevice(Mediator mediator) {
super(mediator);
}
public String read(a){
return data;
}
public void load(a){
data = "Video data, audio data";
// Notify the broker of data changes
mediator.changed(this); }}// Member parts 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); }}/ / the 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 instanceofCpu){ handleCpu((Cpu) colleague); }}/** * Process the drive to read data and interact with other devices *@paramCdDevice Irrigation district equipment */
private void handleCd(CdDevice cdDevice){
mCpu.decodeData(cdDevice.read());
}
/** * Interact with other devices after processing CPU read 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(a) {
// Construct the broker board
MainBoard mainBoard = new MainBoard();
// Build the parts
Cpu cpu = new Cpu(mainBoard);
CdDevice cdDevice = new CdDevice(mainBoard);
GraphicsCard graphicsCard = new GraphicsCard(mainBoard);
SoundCard soundCard = new SoundCard(mainBoard);
// Install the parts on the motherboard
mainBoard.setCdDevice(cdDevice);
mainBoard.setCpu(cpu);
mainBoard.setGraphicsCard(graphicsCard);
mainBoard.setSoundCard(soundCard);
// Play the video when you are readycdDevice.load(); } audio: audio data Video: video dataCopy the code
Memo mode
The memo pattern allows you to save and restore an object’s previous state without exposing its implementation details.
Applicable scenario
- You can 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.
- 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.
implementation
- 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.
- Create the memo class. Declare the memo member variables corresponding to each original member variable one by one.
- Make the memo class immutable. Memos can only receive data once through constructors. The class cannot contain a setter.
- Memos can be nested in the originator if the programming language you are using supports nested classes. If not, extract an empty interface from the memo class and have all other objects reference the memo through the interface. You can add some metadata operations to this interface, but you cannot expose the state of the originator.
- 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.
- 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, you can use the interface as the type of the parameter. In this case, the input object needs to be cast to a memo because the originator needs to have full access to the object.
- 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.
- 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.
Code implementation
// Data model
public class CallOfDuty {
private int mCheckPoint = 1;
private int mLifeValue = 100;
private String mWeapon = "Desert Eagle";
public void play(a) {
System.out.println("Play a game:" + String.format("%s", mCheckPoint) + "In the trenches.");
mLifeValue -= 10;
System.out.println("Game progress upgrade");
mCheckPoint++;
System.out.println("Reach" + String.format("%s", mCheckPoint));
}
public void quit(a) {
System.out.println("-- -- -- -- -- -- -- -- -- --");
System.out.println("Pre-exit stats:" + this.toString());
System.out.println("Quit the game");
System.out.println("-- -- -- -- -- -- -- -- -- --");
}
public Remark createRemark(a) {
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("Restored game attributes:" + this.toString());
}
@Override
public String toString(a) {
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(a) {
return "Remark{" +
"mCheckPoint=" + mCheckPoint +
", mLifeValue=" + mLifeValue +
", mWeapon='" + mWeapon + '\' ' +
'} '; }}// Manage Remark
public class Caretaker {
Remark mRemark;
/** * archive *@paramRemark Memorandum */
public void archive(Remark remark){
this.mRemark = remark;
}
/** * get archive *@returnThe archive * /
public Remark getRemark(a) {
returnmRemark; }}public class DesignPatternsTest {
@Test
public void remarkTest(a) {
// Build the gameobject
CallOfDuty game = new CallOfDuty();
game.play();
/ / archive
Caretaker caretaker = new Caretaker();
caretaker.archive(game.createRemark());
// Exit the game
game.quit();
// Resume the game
CallOfDuty newGame = newCallOfDuty(); newGame.restore(caretaker.getRemark()); }} Playing games: first1The progress of the game reached the first level in the battle of the enemy2Close ---------- before quitting the game attributes: CallOfDuty{mCheckPoint=2, mLifeValue=90, mWeapon=Desert Eagle} quit the game ---------- restored game attributes: CallOfDuty{mCheckPoint=2, mLifeValue=90, mWeapon=Desert Eagle}
Copy the code
Observer model
The Observer pattern defines a subscription mechanism that notifies multiple other objects that “watch” an object when an event occurs.
Applicable scenario
- The observer mode can be used when a change in the state of an object requires notification to other objects, or when the actual object is previously unknown or dynamically changing.
- 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.
implementation
- Declare the subscriber interface, at least one of which is declared
update()
Method, the publisher calls the subscriber’supdate()
Method to notify of updates. - Declare publisher interfaces and define interfaces to add and remove subscription objects from the subscriber list. Publishers must interact with them only through the subscriber interface.
- 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. Specific publishers extend the class to inherit all subscription behavior, but if you need to apply the pattern within an existing class hierarchy, consider a composite approach: put subscription logic into a separate object and then let all actual subscribers use that object.
- Create a concrete publisher class. All subscribers must be notified every time a publisher has an important event.
- 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.
- The client must generate all required subscribers and complete registration with the appropriate publisher.
Code implementation
// 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 subscriber
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(a) {
return "Coder{" +
"name='" + name + '\' ' +
'} '; }}// Specific publisher
public class AndroidDevelopers extends Observable {
public void postNewContent(String content) { notifyObservers(content); }}public class DesignPatternsTest {
@Test
public void observerTest(a) {
Coder coder1 = new Coder("Li bai");
Coder coder2 = new Coder("Du fu");
Coder coder3 = new Coder("Bai Juyi");
Coder coder4 = new Coder("Li Qingzhao");
Coder coder5 = new Coder("Wen Ting-yun");
Coder coder6 = new Coder("Wen Tianxiang");
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 the start"); }} Hi li bai, AndroidDevelopers update content: For example, Compose is composed for AndroidDevelopers. For example, Compose is composed for AndroidDevelopers. For Compose, Hi wen tingjun, AndroidDevelopers are now updating their contentCopy the code
The state pattern
The state pattern changes the behavior of an object as its internal state changes, making it look as if it had changed the class to which it belongs. For specific behavior encapsulated in the state, specific behavior according to its state to achieve different functions.
Applicable scenario
- 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.
- 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.
- State patterns can be used when there is a lot of duplicate code in similar states and conditional based state machine transitions.
implementation
- 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.
- Declare the status interface. While it may be necessary to copy exactly all methods declared in the context, it is best to focus only on those methods that may contain state-specific behavior.
- 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. There are several workarounds for this situation:
- 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. This is simple but convenient, and can be patched up later.
- Nested state classes in context classes. This approach requires that your programming language supports nested classes.
- 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.
- Again examine the methods in context and replace the empty conditional statement with the corresponding state object method.
- To switch context state, you need to create an instance of a state class and pass it to the context. This can be done in context, in various states, or on the client side. Wherever this is done, the class will depend on the concrete class it instantiates.
Code implementation
Take the remote control of a TELEVISION
// The TV interface defines the TV operation behavior function
public interface TvState {
void nextChannel(a);
void prevChannel(a);
void turnUp(a);
void turnDown(a);
}
// The TV is off
public class PowerOffState implements TvState {
@Override
public void nextChannel(a) {
System.out.println("Invalid channel operation in off state \n");
}
@Override
public void prevChannel(a) {
System.out.println("Operation on last channel in shutdown state invalid \n");
}
@Override
public void turnUp(a) {
System.out.println("Invalid to turn up volume in shutdown state \n");
}
@Override
public void turnDown(a) {
System.out.println("Volume down operation in shutdown state is invalid \n"); }}// The TV is off
public class PowerOnState implements TvState {
@Override
public void nextChannel(a) {
System.out.println("Next channel \n");
}
@Override
public void prevChannel(a) {
System.out.println("Go on channel n");
}
@Override
public void turnUp(a) {
System.out.println("Turn up the volume \n");
}
@Override
public void turnDown(a) {
System.out.println("Turn down the volume \n"); }}// Power operation interface
public interface PowerController {
void powerOn(a);
void powerOff(a);
}
public class TvController implements PowerController {
TvState mTvState;
public void setTvState(TvState tvState) {
mTvState = tvState;
}
@Override
public void powerOn(a) {
setTvState(new PowerOnState());
System.out.println("Boot on \n");
}
@Override
public void powerOff(a) {
setTvState(new PowerOffState());
System.out.println("Turned off \n");
}
public void nextChannel(a){
mTvState.nextChannel();
}
public void prevChannel(a){
mTvState.prevChannel();
}
public void turnUp(a){
mTvState.turnUp();
}
public void turnDown(a){ mTvState.turnDown(); }} Turn on next channel up channel Turn down volume Turn up Volume turn off Turn off state Turn up volume operation is invalidCopy the code
The strategy pattern
The policy pattern enables the objects of an algorithm to be interchangeable by defining a series of algorithms and placing each algorithm in a separate class.
Applicable scenario
- You can use policy mode when you work with various algorithm variants in an object and want to be able to switch algorithms at run time. Policy patterns can indirectly change object behavior at run time by associating objects with different child objects that can perform specific subtasks in different ways.
- The policy pattern can be used when there are many similar classes that are only slightly different when performing certain behaviors. The policy pattern can reduce repetitive code by extracting different behaviors into a separate class hierarchy and combining the original classes into one.
- 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.
- This pattern can be used when complex conditional operators are used in a class to switch between variations of the same algorithm.
implementation
- Identify algorithms from context classes that change more frequently (or perhaps complex conditional operators used to select an algorithm variant at run time).
- Declare a common policy interface for all variants of the algorithm.
- The algorithms are extracted one by one into their respective classes, which must implement the policy interface.
- 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.
- The client must associate the context class with the corresponding policy so that the context can do its main job in the expected way.
Code implementation
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 over 10 km
int extraTotal = km - 10;
// Over 5km pair multiple
int extraFactor = extraTotal / 5;
// The distance over is mod 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(a) {}public void setStrategy(CalculateStrategy strategy) {
mStrategy = strategy;
}
public int calculatePrice(int km) {
returnmStrategy.calculatePrice(km); }}public class DesignPatternsTest {
@Test
public void strategyTest(a) {
TranficCalculator tranficCalculator = new TranficCalculator();
tranficCalculator.setStrategy(new SubwayStrategy());
int subwayPrice = tranficCalculator.calculatePrice(10);
System.out.println("Subway travel price of 10 km" + subwayPrice + "Yuan");
tranficCalculator.setStrategy(new BusStrategy());
int busPrice = tranficCalculator.calculatePrice(10);
System.out.println("Subway travel price of 10 km" + busPrice + "Yuan"); }} the subway10Kilometer travel price4Yuan subway10Kilometer travel price1yuanCopy the code
Template method pattern
The template method pattern defines a framework for an algorithm in a superclass, allowing subclasses to override specific steps of the algorithm without modifying the structure.
Applicable scenario
- The template method pattern can be used when you only want the client to extend a particular algorithm step, rather than the entire algorithm or its structure.
- This pattern can be used when the algorithms of multiple classes are almost identical except for minor differences. The consequence, however, is that all classes may need to be modified whenever the algorithm changes.
implementation
- 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.
- 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.
- 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.
- Consider adding hooks between key steps of the algorithm.
- Create a concrete subclass for each algorithm variant that must implement all of the abstract steps or override some of the optional steps.
Code implementation
// Abstract Computer
public abstract class AbstractComputer {
protected void powerOn(a){
System.out.println("Turn on the power");
}
protected void checkHardware(a){
System.out.println("Hardware Check");
}
protected void loadOs(a){
System.out.println("Enter the operating system");
}
protected void login(a){
System.out.println("Little White's computer enters the system without verification.");
}
protected void startUp(a){
System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- on -- -- -- -- -- -- -- -- -- -- -- --");
powerOn();
checkHardware();
loadOs();
login();
System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- shut down -- -- -- -- -- -- -- -- -- -- -- --"); }}// The programmer's computer
public class CoderComputer extends AbstractComputer{
@Override
protected void login(a) {
System.out.println("The programmer only needs user and password authentication to boot up the computer."); }}// Military computer
public class MilitaryComputer extends AbstractComputer {
@Override
protected void checkHardware(a) {
super.checkHardware();
System.out.println("See ha Hardware firewall.");
}
@Override
protected void login(a) {
System.out.println("Perform complex user authentication such as fingerprint identification."); }}public class DesignPatternsTest {
@Test
public void test(a) {
CoderComputer coderComputer = new CoderComputer();
coderComputer.startUp();
MilitaryComputer militaryComputer = newMilitaryComputer(); militaryComputer.startUp(); }} -- -- -- -- -- -- -- -- -- -- -- -- start -- -- -- -- -- -- -- -- -- -- -- -- open source hardware check into the operating system programmer for user and password authentication can restart the computer -- -- -- -- -- -- -- -- -- -- -- -- shut down -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- boot -- -- -- -- -- -- -- -- -- -- -- -- on hardware check See the hardware firewall access to the operating system for complex user authentication, such as fingerprint identification -- -- -- -- -- -- -- -- -- -- -- -- shut down -- -- -- -- -- -- -- -- -- -- -- --Copy the code
Visitor pattern
The visitor pattern isolates the algorithm from the object it works on.
Usage scenarios
- If you need to perform certain operations on all elements in a complex object structure, such as a tree of objects, you can use the Visitor pattern.
- The visitor pattern can be used to clean up the business logic of the auxiliary behavior.
- This pattern can be used when a behavior makes sense only in some classes in a class hierarchy and not in others.
implementation
- Declare a set of “access” methods in the visitor interface, one for each concrete element class in the program.
- 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.
- 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.
- 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.
- Create a concrete visitor class and implement all visitor methods for each behavior that cannot be implemented in an element hierarchy.
- The client must create the visitor object and pass it to the element through the “receive” method.
Code implementation
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(a) {
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(a) {
returnproducts; }}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 + ", lines of code :" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
System.out.println("Manager:" + manager.name + ",kpi:" + manager.kpi+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+Number of new products:+manager.getProducts()); }}public class BusinessReport {
List<Staff> mStaffs = new ArrayList<>();
public BusinessReport(a) {
mStaffs.add(new Manager("Manager Wang"));
mStaffs.add(new Manager("Manager Cui"));
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(a) {
BusinessReport businessReport = new BusinessReport();
System.out.println("---------- Report for CEO ----------");
businessReport.showReport(new CeoVisitor());
System.out.println("---------- Report for CTO ----------");
businessReport.showReport(newCtoVisitor()); ---------- Report for CEO ---------- Manager: Manager Wang, KPI:1098834980, New product quantity:5Manager: Manager Cui, KPI :-1039068197, New product quantity:3Engineer: Engineer - Liu Bei, KPI :-507271997Engineer: Engineer - Guan Yu, KPI:808473843Engineer: Engineer - Zhang Fei, KPI :-231964394---------- Report for CTO ---------- Manager: Manager Wang, KPI:1098834980, New product quantity:5Manager: Manager Cui, KPI :-1039068197, New product quantity:3Engineer: Engineer - Liu Bei, Lines of code:6496Engineer: Engineer - Guan Yu, Lines of code:2636Engineer: Engineer - Zhang Fei, Lines of code:8813
Copy the code