Preface
Tip: Below is the next article to take notes on 23 design modes. For the previous one, go to the home page to view the previous article
17. An overview of behavioral patterns (classification of behavioral patterns)
Behavioral patterns are used to describe complex process control when a program is running, that is, how multiple classes or objects work together to accomplish tasks that cannot be accomplished by a single object. They involve the assignment of responsibilities between algorithms and objects.
Behavioral patterns are categorized into class behavior patterns, which use inheritance mechanisms to assign behaviors among classes, and object behavior patterns, which use combinations or aggregations to assign behaviors among objects. Since the combination or aggregation relationship is less coupled than the inheritance relationship and satisfies the principle of composite reuse, the object behavior pattern is more flexible than the class behavior pattern.
Behavioral patterns are the largest category of GoF design patterns and contain the following 11 patterns.
- Template Method mode: Defines the algorithm skeleton in an operation, delays some steps of the algorithm to a subclass, allowing the subclass to redefine certain steps of the algorithm without changing its structure.
- Strategy mode: A series of algorithms are defined and encapsulated so that they can be replaced with each other, and changes to the algorithms will not affect the customers who use them.
- Command mode: Encapsulates a request as an object, separating the responsibility to make the request from the responsibility to execute it.
- Chain of Responsibility mode: Transfers requests from one object in the chain to the next until the request is responded to. This removes coupling between objects.
- State mode: Allows an object to change its behavior when its internal state changes.
- Observer mode: There is a one-to-many relationship between multiple objects, and when one object changes, it notifies many other objects of the change, thereby affecting the behavior of other objects.
- Mediator mode: Define a mediation object to simplify the interaction between the original objects, reduce the coupling between objects in the system, and make the original objects unnecessary to understand each other.
- Iterator mode: Provides a way to sequentially access a series of data in an aggregated object without exposing its internal representation.
- Visitor mode: Provides multiple access modes for each element in a collection, i.e. multiple visitor objects per element, without changing the elements of the collection.
- Memento mode: Get and save the internal state of an object without destroying its encapsulation for later recovery.
- Interpreter mode: Provides how to define the grammar of a language and how to interpret language sentences, that is, an interpreter.
These 11 behavioral patterns are all object behavioral patterns, except that the template method pattern and the Interpreter pattern are class behavioral patterns. We will describe their characteristics, structure and application in detail below.
18. Detailed explanation of template method mode (template method design mode)
In object-oriented programming, programmers often encounter this situation: when designing a system, they know the key steps required by the algorithm and determine the execution order of these steps, but the implementation of some steps is not yet known, or the implementation of some steps is related to the specific environment.
For example, when going to a bank to handle business, there are generally four processes: number taking, queuing, handling specific business, rating bank staff, etc. Among them, the business of number taking, queuing and rating bank staff is the same for each customer and can be achieved in the parent class, but the specific business is different for different people, it may be deposit, withdrawal or transfer. You can defer implementation to subclasses.
There are many examples of this in life, such as a person getting up, eating, doing things, sleeping, etc. Every day, the content of "doing things" may be different. We define these instances that specify a process or format as templates, allowing users to update them to suit their needs, such as resume templates, paper templates, template files in Word, and so on.
The template approach pattern described below will address similar issues above.
18.1 Definition and Features of Template Method Patterns
The Template Method pattern is defined as follows: Defines the algorithm skeleton in an operation and delays some steps of the algorithm to a subclass so that the subclass can redefine certain steps of the algorithm without changing the structure of the algorithm. It is a class behavior pattern.
The main advantages of this pattern are as follows.
- It encapsulates the invariant part and extends the variable part. It encapsulates algorithms that are considered invariant parts into the parent class, and implements algorithms that are variable parts by inheriting them from subclasses so that subclasses can continue to expand.
- It extracts common parts of code from the parent class to facilitate code reuse.
- Some methods are implemented by subclasses, so subclasses can be extended to add appropriate functionality, which conforms to the open-close principle.
The main drawbacks of this pattern are as follows.
- Each different implementation requires a subclass to be defined, which increases the number of classes, makes the system larger, makes the design more abstract, and indirectly increases the complexity of the system implementation.
- Abstract methods in a parent class are implemented by subclasses, and the results of subclass execution affect the results of the parent class, resulting in a reverse control structure that makes code reading more difficult.
- Due to the shortcomings of the inheritance relationship itself, if the parent class adds a new abstract method, all subclasses will be changed again.
Structure and implementation of 18.2 template method pattern
Template method patterns require attention to collaboration between abstract classes and specific subclasses. It uses polymorphism technology for virtual functions and reverse control technology for "Don't call me, let me call you". Now let's describe their basic structure.
18.2. 1 The structure of the template method pattern
The template method pattern contains the following main roles.
- Abstract Class/Abstract Template
An abstract template class responsible for outlining and skeleton an algorithm. It consists of a template method and several basic methods. These methods are defined as follows.
(1) Template method: Defines the framework of the algorithm and calls the basic methods it contains in some order.
(2) Basic method: It is a step in the whole algorithm and contains the following types.
- Abstract method: declared in an abstract class and implemented by a specific subclass.
- Specific method: Implemented in an abstract class, inherited or overridden in a specific subclass.
- Hook method: Implemented in abstract classes, including logical methods for judgment and empty methods that require subclass overrides.
- Specific Subclass/Specific Implementation (Concrete Class)
Specific implementation classes that implement the abstract methods and hook methods defined in the abstract class are a component step of top-level logic.
The structure diagram of the template method pattern is shown in Figure 1.
18.2. 2 Implementation of template method pattern
The code for the template method pattern is as follows:
public class TemplateMethodPattern { public static void main(String[] args) { AbstractClass tm = new ConcreteClass(); tm.TemplateMethod(); } } //abstract class abstract class AbstractClass { //Template Method public void TemplateMethod() { SpecificMethod(); abstractMethod1(); abstractMethod2(); } //Specific methods public void SpecificMethod() { System.out.println("Specific methods in abstract classes are called..."); } //Abstract Method 1 public abstract void abstractMethod1(); //Abstract Method 2 public abstract void abstractMethod2(); } //Specific subclasses class ConcreteClass extends AbstractClass { public void abstractMethod1() { System.out.println("The implementation of abstract method 1 is called..."); } public void abstractMethod2() { System.out.println("The implementation of abstract method 2 is called..."); } }
The results of the program are as follows:
Specific methods in abstract classes are called...
The implementation of abstract method 1 is called...
The implementation of abstract method 2 is called...
Example application of 18.3 template method pattern
[Example 1] Use the template method mode to implement the procedure design procedures for studying abroad.
Analysis: The procedures for going abroad to study generally go through the following processes: obtaining school information, applying for admission, handling for passports, exit cards and certificates for going abroad privately, applying for visas, checking up, booking tickets, preparing to pack, arriving at the target school, etc. Some of them are the same for each school, but some of them are different for different schools. Therefore, it is more suitable to use the template method mode to achieve.
In this example, we first define an abstract class StudyAbroad to study abroad, which contains a template method TemplateMethod(), which contains all the basic methods in the process of going abroad. Some of these methods can be implemented in the abstract class because they are the same in all countries, but some methods are different in different countries. It must be implemented in its specific subclass, such as StudyInAmerica. If you add another country, you only need to add one subclass. Figure 2 shows its structure.
The program code is as follows:
public class StudyAbroadProcess { public static void main(String[] args) { StudyAbroad tm = new StudyInAmerica(); tm.TemplateMethod(); } } //Abstract Class: Study Abroad abstract class StudyAbroad { public void TemplateMethod() //Template Method { LookingForSchool(); //Request school information ApplyForEnrol(); //Application for Admission ApplyForPassport(); //Handle exit passports, exit cards and notarization for private reasons ApplyForVisa(); //Apply for a visa ReadyGoAbroad(); //Check-up, booking tickets, preparing packages Arriving(); //Arrive } public void ApplyForPassport() { System.out.println("three.Processing passports, exit cards and notarization for private purposes:"); System.out.println(" 1)Apply to the public security organ of the place where the residence is located for handling the passport and exit card for private reasons by holding the acceptance notice, my residence book or identity card."); System.out.println(" 2)Handle the notarization of birth certificates, academic qualifications, academic degrees and achievements, experience certificates, relatives and economic guarantees."); } public void ApplyForVisa() { System.out.println("four.Apply for a visa:"); System.out.println(" 1)Prepare all kinds of materials needed to apply for foreign visas, including certificates of personal qualifications, report cards and work experience; Individual and family income, funds and property certificates; Family member's relationship certificate, etc."); System.out.println(" 2)Ambassador to China to a Country Ready to Study(collar)The library applies for an entry visa. When applying, you must fill in the relevant forms as required, submit the necessary supporting materials and pay the visa. Some countries(Such as the United States, the United Kingdom, Canada and so on)When applying for a visa, the applicant is required to go to the envoy(collar)The library has an interview."); } public void ReadyGoAbroad() { System.out.println("five.Check-up, booking tickets, preparation for packing:"); System.out.println(" 1)Conduct physical examinations, immune tests and vaccination against infectious diseases;"); System.out.println(" 2)Determine the ticket time, flight and transfer location."); } public abstract void LookingForSchool();//Request school information public abstract void ApplyForEnrol(); //Application for Admission public abstract void Arriving(); //Arrive } //Specific Subcategory: Study in the United States class StudyInAmerica extends StudyAbroad { @Override public void LookingForSchool() { System.out.println("one.The following information was obtained from the school:"); System.out.println(" 1)To have a comprehensive understanding of the political, economic, cultural background, educational system and academic level of the country with the intention to study abroad;"); System.out.println(" 2)Fully understand and master the situation of foreign schools, including history, tuition, education system, specialty, faculty, teaching facilities, academic status, number of students, etc."); System.out.println(" 3)Learn about the accommodation, transportation and medical insurance of the school;"); System.out.println(" 4)Does the school have an authorized agent for enrollment in China?"); System.out.println(" 5)Master the status of study abroad visas;"); System.out.println(" 6)Does the government allow international students to work legally?"); System.out.println(" 8)Can I migrate after graduation?"); System.out.println(" 9)Is the diploma recognized by our country?"); } @Override public void ApplyForEnrol() { System.out.println("two.Application for Admission:"); System.out.println(" 1)Fill in the registration form;"); System.out.println(" 2)Send the application form, personal qualification certificate, recent academic record, recommendation letter, resume, TOEFL or IELTS language test results to the school you are applying for."); System.out.println(" 3)In order to allow enough time for visa processing, it is recommended that the earlier the application is, the better. Generally, it is easier to apply one year in advance."); } @Override public void Arriving() { System.out.println("six.Arrive at target school:"); System.out.println(" 1)Arrange accommodation;"); System.out.println(" 2)Learn about the campus and its surroundings."); } }
The results of the program are as follows:
1. The following information was obtained from the school:
1) To have a more comprehensive understanding of the political, economic, cultural background, educational system and academic level of the country with the intention to study abroad;
2) To fully understand and master the situation of foreign schools, including history, tuition, education system, specialty, faculty, teaching facilities, academic status, number of students, etc.
3) Understand the accommodation, transportation and medical insurance conditions of the school;
4) Does the school have an authorized overseas study agency in China?
5) Master the status of study abroad visas;
6) Does the government allow international students to work legally?
8) Can I migrate after graduation?
9) Is the diploma recognized by China?
2. Application for Admission:
1) Fill in the registration form;
2) Send the application form, personal qualification certificate, recent academic record, letter of recommendation, personal resume, TOEFL or IELTS language test results to the school to which you apply;
3) In order to allow sufficient time for visa processing, it is recommended that the sooner the application is, the better. Generally, it is easier to apply one year in advance.
3. Processing passports, exit cards and notarization for private purposes:
1) Apply to the public security organ at the place where the residence registration is located for handling the passport and exit card for the purpose of leaving the country privately on the basis of the acceptance notice, the residence book or the identity card.
2) Handle the notarization of birth certificates, academic qualifications, academic degrees and achievements, experience certificates, relatives and economic guarantees.
4. Apply for a visa:
(1) All kinds of materials needed to prepare for the application for a foreign visa, including certificates of one's academic qualifications, report cards and work experience; Individual and family income, funds and property certificates; Family member's relationship certificate, etc.
2) To apply for an entry visa from the Embassy (consulate) of the country to study in China. When applying, you must fill in the relevant forms as required, submit the necessary supporting materials and pay the visa. Some countries (such as the United States, the United Kingdom, Canada, etc.) require applicants to go to embassies for interviews when applying for visas.
5. Check-up, booking tickets, preparation for packing:
1) Conduct physical examination, immunological examination and vaccination against infectious diseases;
2) Determine the ticket time, flight and transfer location.
Six. Arrive at target school:
1) Arrange accommodation;
2) Understanding the campus and its surroundings.
Scenarios for 18.4 Template Method Patterns
Template method patterns are generally applicable in the following scenarios.
- The overall steps of the algorithm are fixed, but when individual parts of the algorithm are volatile, the template method pattern can be used to abstract the volatile parts for subclasses to implement.
- When multiple subclasses have common behavior, they can be extracted and aggregated into a common parent class to avoid code duplication. First, identify the differences in the existing code and separate them into new operations. Finally, replace these different codes with a template method that calls these new operations.
- When you need to control the extension of subclasses, template methods invoke hook operations only at specific points, allowing expansion only at those points.
Extensions to the 18.5 template method pattern
In the template method pattern, the basic methods include abstract methods, concrete methods, and hook methods. Proper use of the hook method allows subclasses to control the behavior of the parent class. In the following example, the operation results in an abstract parent class can be altered by overriding the hook methods HookMethod1() and HookMethod2() in a specific subclass, as shown in Figure 3.
The program code is as follows:
public class HookTemplateMethod { public static void main(String[] args) { HookAbstractClass tm = new HookConcreteClass(); tm.TemplateMethod(); } } //Abstract class with hook method abstract class HookAbstractClass { //Template Method public void TemplateMethod() { abstractMethod1(); HookMethod1(); if (HookMethod2()) { SpecificMethod(); } abstractMethod2(); } //Specific methods public void SpecificMethod() { System.out.println("Specific methods in abstract classes are called..."); } //Hook Method 1 public void HookMethod1() { } //Hook Method 2 public boolean HookMethod2() { return true; } //Abstract Method 1 public abstract void abstractMethod1(); //Abstract Method 2 public abstract void abstractMethod2(); } //Specific subclasses with hook methods class HookConcreteClass extends HookAbstractClass { public void abstractMethod1() { System.out.println("The implementation of abstract method 1 is called..."); } public void abstractMethod2() { System.out.println("The implementation of abstract method 2 is called..."); } public void HookMethod1() { System.out.println("Hook method 1 is overridden..."); } public boolean HookMethod2() { return false; } }
The results of the program are as follows:
The implementation of abstract method 1 is called...
Hook method 1 is overridden...
The implementation of abstract method 2 is called...
If the code for the hook method HookMethod1() and hook method HookMethod2() changes, the results of the program will also change.
18.6 My Template Method Practice
Abstract Class:
package com.xjc.factory.templatemethod; //abstract class public abstract class Cooking { //Template Method public void cook() { start(); step1(); step2(); end(); } //Method 1 void start() { System.out.println("Start cooking======"); System.out.println("Hot pot"); } //Method 2 void end() { System.out.println("Packing"); System.out.println("Wash pot and stove"); } //Abstract Method 1 abstract void step1(); //Abstract Method 2 abstract void step2(); }
Specific Class 1:
package com.xjc.factory.templatemethod; //Cooking noodles public class Noodle extends Cooking{ @Override void step1() { System.out.println("Boil with water"); } @Override void step2() { System.out.println("Put in noodles, spices"); } }
Specific Class 2:
package com.xjc.factory.templatemethod; //Scrambled potatoes public class Potatoes extends Cooking{ @Override void step1() { System.out.println("Oil Filling"); } @Override void step2() { System.out.println("Add potatoes, seasoning"); } }
Client:
package com.xjc.factory.templatemethod; public class Client { public static void main(String[] args) { System.out.println("Cook noodles================================================="); Cooking noodle = new Noodle(); noodle.cook(); System.out.println(); System.out.println("Fried potatoes================================================="); Cooking potatoes = new Potatoes(); potatoes.cook(); } }
Run result:
19. Detailed explanation of strategy mode (strategy design mode)
In real life, we often encounter situations where there are many strategies to achieve a certain goal, such as traveling by air, by train, by bicycle or by private car. Supermarket promotion can use methods such as discounting, delivering goods, and delivering credits.
Similar situations are often encountered in software development. When there are many algorithms or strategies to implement a certain function, we can select different algorithms or strategies according to different environments or conditions to complete the function, such as bubble sorting, selection sorting, insert sorting, binary tree sorting, and so on.
If implemented using multiple conditional transfer statements (i.e., hard-coded), not only complicates conditional statements, but also adds, deletes, or replaces algorithms that modify the original code, make it difficult to maintain and violate the open-close principle. This problem can be well solved by using a policy mode.
Definition and Characteristics of 19.1 Policy Patterns
strategy Definition of a (Strategy) pattern: The pattern defines a series of algorithms and encapsulates each one so that they can be replaced with each other, and changes in the algorithm will not affect customers who use the algorithm. A strategy pattern is an object behavior pattern that encapsulates the algorithm, splits responsibility for using the algorithm from its implementation, and delegates it to different objects These algorithms are managed.
The main advantages of the strategy pattern are as follows.
- Multiple conditional statements are not easy to maintain, and using a policy pattern avoids multiple conditional statements, such as if...else, switch...case.
- Policy mode provides a series of reusable algorithm families, and proper use of inheritance can transfer common code of the algorithm family into the parent class, thereby avoiding duplicate code.
- Strategic patterns can provide different implementations of the same behavior, and customers can choose from different time or space requirements.
- The policy mode provides perfect support for the open and close principle, allowing for the flexible addition of new algorithms without modifying the original code.
- The strategy mode places the use of the algorithm in the environment class, while the implementation of the algorithm moves to the specific strategy class to separate the two.
Its main drawbacks are as follows.
- Clients must understand the differences between all policy algorithms in order to select the appropriate algorithm class in time.
- Policy patterns result in a large number of policy classes, which make maintenance more difficult.
Structure and Implementation of 19.2 Policy Mode
A policy pattern is to prepare a set of algorithms and encapsulate them in a series of policy classes as a subclass of an abstract policy class. The focus of the strategy mode is not how to implement the algorithms, but how to organize them to make the program structure more flexible, more maintainable and extensible. Now let's analyze their basic structure and implementation methods.
19.2. 1 Structure of Policy Patterns
The main roles of the strategy pattern are as follows.
- Abstract Strategy class: Defines a common interface that is implemented in different ways by different algorithms. Environment roles use this interface to invoke different algorithms, usually using interfaces or abstract classes.
- Specific Strategy class: implements the interface of abstract policy definitions and provides specific algorithm implementations.
- Context class: Holds a reference to a policy class that is ultimately invoked to the client.
The structure diagram is shown in Figure 1.
19.2. Implementation of the 2-mode
The implementation code for the policy pattern is as follows:
public class StrategyPattern { public static void main(String[] args) { Context c = new Context(); Strategy s = new ConcreteStrategyA(); c.setStrategy(s); c.strategyMethod(); System.out.println("-----------------"); s = new ConcreteStrategyB(); c.setStrategy(s); c.strategyMethod(); } } //Abstract Policy Class interface Strategy { public void strategyMethod(); //Strategic approach } //Specific Policy Class A class ConcreteStrategyA implements Strategy { public void strategyMethod() { System.out.println("Specific strategies A The policy method of was accessed!"); } } //Specific Policy Class B class ConcreteStrategyB implements Strategy { public void strategyMethod() { System.out.println("Specific strategies B The policy method of was accessed!"); } } //Environment Class class Context { private Strategy strategy; public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void strategyMethod() { strategy.strategyMethod(); } }
The program runs as follows:
Strategic approaches for specific strategy A are accessed!
Policy methods for specific Policy B are accessed!
Application examples of 19.3 policy patterns
[Example 1] The application of strategy mode in cooking hairy crab.
Analysis: There are many methods about hairy crab. We take steaming hairy crab and burning hairy crab as examples to introduce the application of the strategy mode.
First, define an abstract strategy class (CrabCooking) for haircrab processing, which contains an abstract cooking method CookingMethod(); Then? Define specific policy classes for Steamed Crabs and Braised Crabs, which implement abstract methods in abstract policy classes; define specific policy classes as subclasses of JLabel because this program displays a good result map (click here to download the result map to display); and finally, define a kitchen (Kitchen) Environment class, which has methods for setting and choosing cooking strategies; Customer class obtains cooking strategies through the kitchen class, and displays the cooking result graph in the form. Figure 2 shows its structure.
The program code is as follows:
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class CrabCookingStrategy implements ItemListener { private JFrame f; private JRadioButton qz, hs; private JPanel CenterJP, SouthJP; private Kitchen cf; //Kitchen private CrabCooking qzx, hsx; //Crab Processor CrabCookingStrategy() { f = new JFrame("Application of strategy mode in hairy crab cooking"); f.setBounds(100, 100, 500, 400); f.setVisible(true); f.setResizable(false); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); SouthJP = new JPanel(); CenterJP = new JPanel(); f.add("South", SouthJP); f.add("Center", CenterJP); qz = new JRadioButton("Stewed Big Fresh Water Crab"); hs = new JRadioButton("Braised crab"); qz.addItemListener(this); hs.addItemListener(this); ButtonGroup group = new ButtonGroup(); group.add(qz); group.add(hs); SouthJP.add(qz); SouthJP.add(hs); //--------------------------------- cf = new Kitchen(); //Kitchen qzx = new SteamedCrabs(); //Steamed hairy crabs hsx = new BraisedCrabs(); //Braised Crabs } public void itemStateChanged(ItemEvent e) { JRadioButton jc = (JRadioButton) e.getSource(); if (jc == qz) { cf.setStrategy(qzx); cf.CookingMethod(); //Steaming } else if (jc == hs) { cf.setStrategy(hsx); cf.CookingMethod(); //Red fever } CenterJP.removeAll(); CenterJP.repaint(); CenterJP.add((Component) cf.getStrategy()); f.setVisible(true); } public static void main(String[] args) { new CrabCookingStrategy(); } } //Abstract Policy Class: Crab Processing Class interface CrabCooking { public void CookingMethod(); //Cooking methods } //Specific strategies: Steaming hairy crab class SteamedCrabs extends JLabel implements CrabCooking { private static final long serialVersionUID = 1L; public void CookingMethod() { this.setIcon(new ImageIcon("src/strategy/SteamedCrabs.jpg")); this.setHorizontalAlignment(CENTER); } } //Specific strategies: braised crab class BraisedCrabs extends JLabel implements CrabCooking { private static final long serialVersionUID = 1L; public void CookingMethod() { this.setIcon(new ImageIcon("src/strategy/BraisedCrabs.jpg")); this.setHorizontalAlignment(CENTER); } } //Environment Class: Kitchen class Kitchen { private CrabCooking strategy; //Abstract Policy public void setStrategy(CrabCooking strategy) { this.strategy = strategy; } public CrabCooking getStrategy() { return strategy; } public void CookingMethod() { strategy.CookingMethod(); //Cook a dish } }
The result of running the program is shown in Figure 3.
[Example 2] Use the strategy mode to realize the travel mode from Shaoguan to Wuyuan.
Analysis: There are several ways to travel from Shaoguan to Wuyuan: by train, by car and by car, so this example is more suitable with the strategy mode. Figure 4 shows its structure.
Scenarios for the 19.4 Policy Mode
Policy patterns are used in many places, such as container layout management in Java SE, where each container has multiple layouts for the user to choose from. In programming, policy patterns are often used more often in the following situations.
When a system needs to dynamically select one of several algorithms, each algorithm can be encapsulated in a policy class.
A class defines a variety of behaviors that occur in the form of multiple conditional statements in its operations, and each conditional branch can be moved into their respective policy classes in place of these conditional statements.
The algorithms in the system are completely independent of each other and require that the implementation details of specific algorithms be hidden from the client.
When the system requires that customers who use algorithms should not know the data they operate on, a policy pattern can be used to hide the data structures associated with the algorithms.
Multiple classes differ only by what is currently in the table. You can use a policy pattern to dynamically select specific behaviors to perform at run time.
Extensions to the 19.5 Policy Mode
In a system that uses policy mode, when there are many policies, client management of all policy algorithms becomes complex. Using policy factory mode to manage these policy classes in environment classes will greatly reduce the complexity of the client's work, as shown in Figure 5.
19.6 My Strategic Mode Practice
Abstract Policy Class, Payment Policy:
package com.xjc.factory.strategymode; //Abstract Policy Class, Payment Policy public interface PayStrategy { void pay(); }
Payment Policy Factory
package com.xjc.factory.strategymode; //Payment Policy Factory public class PayStrategyFactory implements PayStrategy{ private PayStrategy payStrategy; public PayStrategyFactory(PayStrategy payStrategy) { this.payStrategy = payStrategy; } @Override public void pay() { payStrategy.pay(); } }
Specific strategy: Alibaba pay
package com.xjc.factory.strategymode; //Specific strategy: Alibaba pay public class AliPay implements PayStrategy{ @Override public void pay() { System.out.println("Pay with Alipay"); } }
Specific strategy: cash payment
package com.xjc.factory.strategymode; //Specific strategy: cash payment public class CashPay implements PayStrategy{ @Override public void pay() { System.out.println("Cash payment"); } }
Specific strategy: UnionPayment
package com.xjc.factory.strategymode; //Specific strategy: UnionPayment public class UnionPay implements PayStrategy{ @Override public void pay() { System.out.println("Use UnionPay"); } }
Specific strategy: WeChat payment
package com.xjc.factory.strategymode; //Specific strategy: WeChat payment public class WeChatPay implements PayStrategy{ @Override public void pay() { System.out.println("Pay using WeChat"); } }
Client
package com.xjc.factory.strategymode; //Client public class Client { public static void main(String[] args) { System.out.println("WeChat:"); new PayStrategyFactory(new WeChatPay()).pay(); System.out.println("Alipay:"); new PayStrategyFactory(new AliPay()).pay(); System.out.println("Cash:"); new PayStrategyFactory(new CashPay()).pay(); System.out.println("UnionPay:"); new PayStrategyFactory(new UnionPay()).pay(); } }
Run result:
20. Command Mode (Detailed Edition)
In software development systems, there is often a close coupling between the requestor of a method and the implementer of a method, which is not conducive to the expansion and maintenance of software functions. For example, it is inconvenient to "undo, redo, record" a method, and so on, so "How do I decouple the requestor and the implementer of the method?" It becomes important that command mode does a good job of solving this problem.
In real life, there are many examples of command mode. For example, when watching TV, we can switch channels with a single touch of the remote control. This is command mode, which completely decouples the request for a change of stations from the process of changing stations. The TV remote control (command sender) remotely controls the TV (command receiver) by means of buttons (specific commands).
For example, when we go to a restaurant for dinner, the menu is not customized until the guests arrive, but pre-configured. In this way, guests will only be asked to order, not to be customized on a temporary basis. The menu provided by the restaurant is equivalent to decoupling requests from processing, which is the manifestation of the command mode.
Definition and Features of 20.1 Command Mode
The Command mode is defined as follows: encapsulating a request as an object separates the responsibility for making the request from the responsibility for executing the request, so that the two communicate through the command object, which facilitates the storage, transfer, invocation, addition and management of the command object.
The main advantages of command mode are as follows.
- Decrease system coupling by introducing middleware (abstract interface).
- Extensibility is good, and adding or deleting commands is easy. Adding and deleting commands in command mode does not affect other classes and meets the Open and Close Principle.
- Macro commands can be implemented. Command mode can be combined with combination mode to assemble multiple commands into one combination command, namely macro command.
- Convenient implementation of Undo and Redo operations. The command mode can be combined with the memo mode described later to revoke and restore the command.
- Additional functionality can be added to existing commands. Logging, for example, is more flexible when combined with the decorator mode.
Its drawbacks are:
- A large number of specific command classes may result. Because each operation requires a specific command class to be designed, this increases the complexity of the system.
- The result of command mode is the execution result of the receiver, but in order to architect, decouple requests, and implement them as commands, Additional type structures (the introduction of the requestor and abstract command interfaces) make understanding more difficult. However, this is also a common problem with design patterns, abstraction necessarily increases the number of additional classes, and code extraction is certainly more difficult to understand than code aggregation.
Structure and implementation of 20.2 command mode
The related operations in the system can be abstracted as commands to separate the caller from the implementer, and the structure is as follows.
20.2. 1 Structure of command mode
Command mode contains the following main roles.
- Command role: Declares the interface to execute commands, and has the abstract method execute() to execute commands.
- Specific Command Role: A concrete implementation class of an abstract command class that owns a recipient object and completes the action the command is to perform by calling the recipient's functions.
- Implementer/Receiver role: Actions that perform command functions are the real implementers of a specific command object business.
- Invoker role: is the sender of a request. It usually has many command objects and executes related requests by accessing them. It does not directly access the recipient.
The structure diagram is shown in Figure 1.
20.2. 2 Implementation of Command Mode
The code for the command mode is as follows:
package command; public class CommandPattern { public static void main(String[] args) { Command cmd = new ConcreteCommand(); Invoker ir = new Invoker(cmd); System.out.println("Client Access Caller's call()Method..."); ir.call(); } } //caller class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void setCommand(Command command) { this.command = command; } public void call() { System.out.println("Caller Executes Command command..."); command.execute(); } } //Abstract command interface Command { public abstract void execute(); } //order of the day class ConcreteCommand implements Command { private Receiver receiver; ConcreteCommand() { receiver = new Receiver(); } public void execute() { receiver.action(); } } //Recipient class Receiver { public void action() { System.out.println("Receiver's action()Method Called..."); } }
The results of the program are as follows:
Customer access caller's call() method...
Caller executes command...
The recipient's action() method is called...
Application examples of 20.3 command mode
[Example 1] An example of a customer going to a restaurant for breakfast using command mode.
Analysis: Customers can choose intestinal flour, River flour and wonton for breakfast when they go to the restaurant. Customers can choose several of the above breakfast types from the waiters. The waiters will hand the customer's request to the relevant chefs to do it. A breakfast order here is equivalent to a command, a waiter is equivalent to a caller, and a cook is equivalent to a receiver, so it's appropriate to use the command mode.
- First, define a Breakfast, which is an abstract command class with an abstract cooking() method to explain what to do.
- Define its subclasses ChangFen, HunTun and HeFen, which are specific commands to implement the cooking() method for breakfast, but they are not specific, but assigned to specific chefs.
- Specific types of chefs are ChangFenChef, HunTunChef and HeFenChef, who are the recipients of the order.
Because this example shows the effect diagram of a chef's cooking (click here to download the effect diagram to display), each chef class is defined as a subclass of JFrame. Finally, the Waiter class is defined, which receives a customer's cooking request and issues a cooking command. The customer class is ordered through the server class, and the structure diagram shown in Figure 2 is shown.
The program code is as follows:
package command; import javax.swing.*; public class CookingCommand { public static void main(String[] args) { Breakfast food1 = new ChangFen(); Breakfast food2 = new HunTun(); Breakfast food3 = new HeFen(); Waiter fwy = new Waiter(); fwy.setChangFen(food1);//Set up flour menu fwy.setHunTun(food2); //Set up river flour menu fwy.setHeFen(food3); //Set up wonton menu fwy.chooseChangFen(); //Choose Intestinal Powder fwy.chooseHeFen(); //Select River flour fwy.chooseHunTun(); //Choose Wonton } } //Caller: Server class Waiter { private Breakfast changFen, hunTun, heFen; public void setChangFen(Breakfast f) { changFen = f; } public void setHunTun(Breakfast f) { hunTun = f; } public void setHeFen(Breakfast f) { heFen = f; } public void chooseChangFen() { changFen.cooking(); } public void chooseHunTun() { hunTun.cooking(); } public void chooseHeFen() { heFen.cooking(); } } //Abstract command: breakfast interface Breakfast { public abstract void cooking(); } //Specific commands: Intestinal flour class ChangFen implements Breakfast { private ChangFenChef receiver; ChangFen() { receiver = new ChangFenChef(); } public void cooking() { receiver.cooking(); } } //Specific command: Wonton class HunTun implements Breakfast { private HunTunChef receiver; HunTun() { receiver = new HunTunChef(); } public void cooking() { receiver.cooking(); } } //Specific command: River powder class HeFen implements Breakfast { private HeFenChef receiver; HeFen() { receiver = new HeFenChef(); } public void cooking() { receiver.cooking(); } } //Receiver: Intestinal flour Chef class ChangFenChef extends JFrame { private static final long serialVersionUID = 1L; JLabel l = new JLabel(); ChangFenChef() { super("Boiled sausage powder"); l.setIcon(new ImageIcon("src/command/ChangFen.jpg")); this.add(l); this.setLocation(30, 30); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void cooking() { this.setVisible(true); } } //Recipient: Wonton Chef class HunTunChef extends JFrame { private static final long serialVersionUID = 1L; JLabel l = new JLabel(); HunTunChef() { super("Cooking wonton"); l.setIcon(new ImageIcon("src/command/HunTun.jpg")); this.add(l); this.setLocation(350, 50); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void cooking() { this.setVisible(true); } } //Receiver: River flour Chef class HeFenChef extends JFrame { private static final long serialVersionUID = 1L; JLabel l = new JLabel(); HeFenChef() { super("Boil River flour"); l.setIcon(new ImageIcon("src/command/HeFen.jpg")); this.add(l); this.setLocation(200, 280); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void cooking() { this.setVisible(true); } }
The result of the program is shown in Figure 2.
20.4 Command Mode Scenarios
When an operation of the system has command semantics and command implementations are unstable (changing), requests and implementations can be decoupled through command mode. An Abstract command interface is used to stabilize the code architecture of the requestor and encapsulate the details of the implementation of the specific commands of the recipient. Receiver and abstract commands are weakly coupled (internal methods do not need to be consistent) and have good scalability.
Command mode is generally applicable in the following scenarios.
- Command mode allows callers and receivers not to interact directly when the request caller needs to be decoupled from the request receiver.
- When the system requests commands randomly or frequently adds or deletes commands, the command mode can easily implement these functions.
- Command mode defines macro commands to perform this function when the system needs to perform a set of operations.
- When the system needs to support command Undo and Redo operations, the command object can be stored and implemented in memo mode.
Extensions to 20.5 Command Mode
In software development, sometimes the command mode is combined with the previous combination mode, which constitutes the macro command mode, also known as the combination command mode. A macro command contains a set of commands that act as both a specific command and a caller. When executed, it recursively calls all the commands it contains. The specific structure of the macro command is shown in Figure 3.
The program code is as follows:
package command; import java.util.ArrayList; public class CompositeCommandPattern { public static void main(String[] args) { AbstractCommand cmd1 = new ConcreteCommand1(); AbstractCommand cmd2 = new ConcreteCommand2(); CompositeInvoker ir = new CompositeInvoker(); ir.add(cmd1); ir.add(cmd2); System.out.println("Client Access Caller's execute()Method..."); ir.execute(); } } //Abstract command interface AbstractCommand { public abstract void execute(); } //Leaf Component: Specific Command 1 class ConcreteCommand1 implements AbstractCommand { private CompositeReceiver receiver; ConcreteCommand1() { receiver = new CompositeReceiver(); } public void execute() { receiver.action1(); } } //Leaf Component: Specific Command 2 class ConcreteCommand2 implements AbstractCommand { private CompositeReceiver receiver; ConcreteCommand2() { receiver = new CompositeReceiver(); } public void execute() { receiver.action2(); } } //Branch Component: Caller class CompositeInvoker implements AbstractCommand { private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>(); public void add(AbstractCommand c) { children.add(c); } public void remove(AbstractCommand c) { children.remove(c); } public AbstractCommand getChild(int i) { return children.get(i); } public void execute() { for (Object obj : children) { ((AbstractCommand) obj).execute(); } } } //Recipient class CompositeReceiver { public void action1() { System.out.println("Receiver's action1()Method Called..."); } public void action2() { System.out.println("Receiver's action2()Method Called..."); } }
The results of the program are as follows:
Customer access caller's execute() method...
The recipient's action1() method was called...
The recipient's action2() method was called...
Of course, command mode can also be used in combination with memo mode, which makes it an undoable command mode, which will be described later.
20.6 My Command Mode Practice
Abstract commands:
package com.xjc.factory.command; //Abstract command public interface Command { void execute(); }
Specific command 1:
package com.xjc.factory.command; //Specific commands: Add public class AddCommand implements Command { private AddReceiver addReceiver; public AddCommand() { addReceiver = new AddReceiver(); } @Override public void execute() { addReceiver.action(); } }
Specific command 2:
package com.xjc.factory.command; //Specific commands: reduce public class ReduceCommand implements Command{ private ReduceReceiver reduceReceiver; public ReduceCommand() { reduceReceiver=new ReduceReceiver(); } @Override public void execute() { reduceReceiver.action(); } }
Receiver 1:
package com.xjc.factory.command; //Receiver: Perform command function related operations, is the real implementer of specific command object business. public class AddReceiver { public void action() { System.out.println("increase"); } }
Receiver 2:
package com.xjc.factory.command; //Receiver: Perform command function related operations, is the real implementer of specific command object business. public class ReduceReceiver { public void action() { System.out.println("reduce"); } }
Caller:
package com.xjc.factory.command; //caller public class Invoker { private Command command; //Receive a command public Invoker(Command command) { this.command = command; } public void setCommand(Command command) { this.command = command; } //Execute Command public void call() { command.execute(); } }
Client Code:
package com.xjc.factory.command; public class CommandPattern { public static void main(String[] args) { //Create Command Command cmd=new AddCommand(); //Pass command to caller Invoker invoker = new Invoker(cmd); //The caller executes the command (the real person performing the command operation is the receiver) invoker.call(); invoker = new Invoker(new ReduceCommand()); invoker.call(); } }
Run result:
21. Detailed explanation of responsibility chain model (responsibility chain model)
In real life, it is common for an event to be processed by multiple objects. For example, purchase approval process, leave process, etc. Employees of a company take time off. Leaders of departments, deputy general managers and general managers can grant leave. However, the number of days each leader can approve is different. Employees must find different signatures of leaders according to the number of days they need to take time off. That is to say, employees must remember the name, phone and address of each leader, which undoubtedly increases the difficulty.
There are also related examples in computer software and hardware, such as datagram transmission in bus networks, where each computer decides whether to receive or not based on whether the destination address is the same as its own address. In exception handling, the handler determines whether it handles the exception or not based on its type. There are also interceptors for Struts2, filters for JSP s and Servlet s, all of which can be considered using the responsibility chain model.
Definition and characteristics of 21.1 responsibility chain model
Chain of Responsibility
Responsibility) Definition of a pattern: to avoid coupling the request sender with multiple request handlers, all request handlers are chained together by the previous object remembering the reference of its next object; When a request occurs, it can be passed along the chain until an object handles it.
Note: The responsibility chain model is also called the responsibility chain model.
In the responsibility chain model, the customer only needs to send the request to the responsibility chain, and does not need to care about the details of the request processing and the request delivery process, the request will be delivered automatically. So the chain of responsibility decouples the sender of the request from the handler of the request.
The responsibility chain model is an object behavior model with the following main advantages.
- Decreases the coupling between objects. This pattern makes it unnecessary for an object to know which object handles its request and the structure of the chain, and for the sender and receiver to have explicit information about each other.
- The scalability of the system is enhanced. New request processing classes can be added as needed to satisfy the open and close principle.
- Increased flexibility in assigning responsibilities to objects. When workflows change, members in the chain can be dynamically changed or ordered, and responsibilities can be dynamically added or removed.
- Chain of responsibility simplifies the connection between objects. Each object only needs to maintain a reference to its successors, not to all other processors, which avoids the use of numerous if or if /. else statements.
- Sharing responsibility. Each class only needs to handle its own work which should not be handled to the next object to complete, clarify the scope of responsibilities of each type, and conform to the single responsibility principle of the class.
Its main drawbacks are as follows.
- There is no guarantee that every request will be processed. Since a request does not have an explicit recipient, there is no guarantee that it will be processed. The request may not be processed until it reaches the end of the chain.
- For longer responsibility chains, request processing may involve multiple processing objects, and system performance may be affected.
- The rationality of the responsibility chain is guaranteed by the client, which increases the complexity of the client and may cause system errors due to incorrect set-up of the responsibility chain, such as circular calls.
21.2 Structure and Implementation of Responsibility Chain Model
Usually, the data structure of the responsibility chain pattern can be implemented through a data chain table.
21.2. 1 Structure of responsibility chain model
The responsibility chain model mainly contains the following roles.
- The Abstract Handler role: Defines an interface for processing requests, including an abstract processing method and a subsequent connection.
- Concrete Handler role: Implement the abstract handler's handling method to determine if the request can be processed or if it can be processed, otherwise transfer the request to its successors.
- Client role: Create a processing chain and submit a request to the specific handler object of the chain head, regardless of the details of the processing and the transfer of the request.
The essence of the responsibility chain model is to decouple requests and processes so that requests can be passed and processed in the processing chain. Understanding the responsibility chain model should understand its model, not its specific implementation. The unique feature of the responsibility chain model is that it combines its node handlers into a chain structure and allows the node itself to decide whether to process or forward requests, which is equivalent to letting requests flow.
The structure diagram is shown in Figure 1. Clients can set up a chain of responsibility as shown in Figure 2.
21.2. 2 Realization of Responsibility Chain Model
The implementation code for the responsibility chain pattern is as follows:
package chainOfResponsibility; public class ChainOfResponsibilityPattern { public static void main(String[] args) { //Assembly Responsibility Chain Handler handler1 = new ConcreteHandler1(); Handler handler2 = new ConcreteHandler2(); handler1.setNext(handler2); //Submit Request handler1.handleRequest("two"); } } //Abstract Processor Role abstract class Handler { private Handler next; public void setNext(Handler next) { this.next = next; } public Handler getNext() { return next; } //Methods of handling requests public abstract void handleRequest(String request); } //Processor Role 1 class ConcreteHandler1 extends Handler { public void handleRequest(String request) { if (request.equals("one")) { System.out.println("Processor 1 is responsible for handling the request!"); } else { if (getNext() != null) { getNext().handleRequest(request); } else { System.out.println("No one handled the request!"); } } } } //Processor Role 2 class ConcreteHandler2 extends Handler { public void handleRequest(String request) { if (request.equals("two")) { System.out.println("Processor 2 is responsible for handling the request!"); } else { if (getNext() != null) { getNext().handleRequest(request); } else { System.out.println("No one handled the request!"); } } } }
The program runs as follows:
Processor 2 is responsible for handling the request!
In the code above, we hard-coded messages as String type, whereas in real business, messages are diverse and can be int, String, or custom type. Therefore, on the basis of the above code, message types can be abstracted Request to enhance message compatibility.
Application examples of 21.3 responsibility chain model
[Example 1] Design a leave request approval module with responsibility chain model.
Analysis: If it is stipulated that a student's absence is less than or equal to 2 days, the head teacher can approve it. If it is less than or equal to 7 days, the Dean may approve it. If it is less than or equal to 10 days, the president may approve it. Other circumstances do not permit approval; This instance is suitable for implementation using the responsibility chain pattern.
First, define a Leader, which is an abstract processor that contains a pointer next to the next leader and an abstract processing method handleRequest(int LeaveDays) to handle dummies; Then, define the ClassAdviser, DepartmentHead, and Dean classes, which are subclasses of the abstract handler and the concrete handler. They must implement the handleRequest(int LeaveDays) method of the parent class according to their own powers. If they are not authorized to process, give the false strip to the next specific handler until the end; The Customer class is responsible for creating the processing chain and handing the fake strips to the specific handler of the chain head (Head of Class). Figure 3 shows its structure.
The program code is as follows:
package chainOfResponsibility; public class LeaveApprovalTest { public static void main(String[] args) { //Assembly Responsibility Chain Leader teacher1 = new ClassAdviser(); Leader teacher2 = new DepartmentHead(); Leader teacher3 = new Dean(); //Leader teacher4=new DeanOfStudies(); teacher1.setNext(teacher2); teacher2.setNext(teacher3); //teacher3.setNext(teacher4); //Submit Request teacher1.handleRequest(8); } } //Abstract Processor: Leadership abstract class Leader { private Leader next; public void setNext(Leader next) { this.next = next; } public Leader getNext() { return next; } //Methods of handling requests public abstract void handleRequest(int LeaveDays); } //Specific Processor 1: Class of Headmaster class ClassAdviser extends Leader { public void handleRequest(int LeaveDays) { if (LeaveDays <= 2) { System.out.println("The head teacher approves your leave" + LeaveDays + "Days."); } else { if (getNext() != null) { getNext().handleRequest(LeaveDays); } else { System.out.println("There are too many days off. Nobody approves this holiday!"); } } } } //Specific Processor 2: Department Head Class class DepartmentHead extends Leader { public void handleRequest(int LeaveDays) { if (LeaveDays <= 7) { System.out.println("The Dean approves your leave" + LeaveDays + "Days."); } else { if (getNext() != null) { getNext().handleRequest(LeaveDays); } else { System.out.println("There are too many days off. Nobody approves this holiday!"); } } } } //Specific Processor 3: Presidential Class class Dean extends Leader { public void handleRequest(int LeaveDays) { if (LeaveDays <= 10) { System.out.println("The president approves your leave" + LeaveDays + "Days."); } else { if (getNext() != null) { getNext().handleRequest(LeaveDays); } else { System.out.println("There are too many days off. Nobody approves this holiday!"); } } } } //Handler 4: Dean of Education class DeanOfStudies extends Leader { public void handleRequest(int LeaveDays) { if (LeaveDays <= 20) { System.out.println("The Dean approves your leave" + LeaveDays + "Days."); } else { if (getNext() != null) { getNext().handleRequest(LeaveDays); } else { System.out.println("There are too many days off. Nobody approves this holiday!"); } } } }
The program runs as follows:
The president approves your leave of absence for 8 days.
If an additional Dean class is added, students can be granted 20 days'leave, which is very simple. The code is as follows:
//Processor 4: Dean of Education Class class DeanOfStudies extends Leader { public void handleRequest(int LeaveDays) { if (LeaveDays <= 20) { System.out.println("The Dean approves your leave" + LeaveDays + "Days."); } else { if (getNext() != null) { getNext().handleRequest(LeaveDays); } else { System.out.println("There are too many days off. Nobody approves this holiday!"); } } } }
21.4 Scenarios for applying the responsibility chain model
The structure and characteristics of the responsibility chain model have been described before. The following scenarios describe its application. The responsibility chain model is often used in the following situations.
- Multiple objects can process a request, but which object handles the request automatically at runtime.
- You can dynamically specify a set of objects to process requests or add new processors.
- Requests need to be submitted to one of multiple handlers without explicitly specifying a request handler.
Extension of 21.5 Chain of Responsibility Model
There are two scenarios for the responsibility chain model.
- Pure chain of responsibility mode: a request must be received by a certain handler object, and a specific handler can only handle a request by one of the following two actions: handling it by himself (assuming responsibility); and transferring responsibility to the next person for processing.
- Impure Chain of Responsibility mode: Allows a specific handler object to pass on the remaining responsibility to his or her family after assuming part of the responsibility for the request, and a request may ultimately not be received by any recipient object.
21.6 Practice of My Responsibility Chain Model
Abstract Processor Role
package com.xjc.factory.chainofresponsibility; //Abstract Processor Role public abstract class Handler { private Handler nextHandler; public void setNextHandler(Handler nextHandler) { this.nextHandler = nextHandler; } public Handler getNextHandler() { return nextHandler; } abstract void handlerRequest(int days); }
Processor 1: Team Leader
package com.xjc.factory.chainofresponsibility; //Processor 1: Team Leader public class GroupLeader extends Handler{ @Override void handlerRequest(int days) { if (days <= 3) { System.out.println("Leave less than or equal to three days, the team leader is eligible to approve!!!"); }else { if (getNextHandler() != null) { getNextHandler().handlerRequest(days); }else { System.out.println("You're taking a strange number of days off. Nobody can handle it!!"); } } } }
Processor 2: Manager
package com.xjc.factory.chainofresponsibility; //Processor 2: Manager public class Manager extends Handler{ @Override void handlerRequest(int days) { if (days <= 10) { System.out.println("Leave less than or equal to ten days, manager is qualified to approve!!!"); }else { if (getNextHandler() != null) { getNextHandler().handlerRequest(days); }else { System.out.println("You're taking a strange number of days off. Nobody can handle it!!"); } } } }
Client
package com.xjc.factory.chainofresponsibility; //Client public class Client { public static void main(String[] args) { //Create Group Leader GroupLeader groupLeader = new GroupLeader(); //Create Manager Manager manager = new Manager(); //Set the next handler for the group leader groupLeader.setNextHandler(manager); groupLeader.handlerRequest(3); groupLeader.handlerRequest(10); groupLeader.handlerRequest(11); } }
Run result:
22. State Mode
During the software development process, some objects in the application may behave differently according to different situations. We call such objects stateful, and we call one or more dynamic properties that affect the behavior of objects state. When a stateful object interacts with external events, its internal state changes, thus changing its behavior. If people have happy and sad times, different moods have different behaviors, of course, the outside world will also affect their mood changes.
For this stateful object programming, the traditional solution is to take all of these possible situations into account, then use if-else or switch-case statements to make state judgments, and then deal with different situations. However, it is obvious that this method has a natural disadvantage to complex state judgment. Conditional judgment statements are too bloated, can be read poorly, do not have scalability, and are difficult to maintain. Adding a new if-else statement when adding a new state violates the Open and Close Principle and is not conducive to program expansion.
The above problems can be solved well by using the state mode. The solution to the state mode is: when the conditional expression controlling the state transition of an object is too complex, extract the relevant "judgment logic" and represent it with different classes. In which case the system is in, process it directly with the corresponding state class object. This can simplify the original complex logical judgment, eliminating if-else, Redundant statements such as switch-case make the code more hierarchical and scalable.
22.1 Definition and Characteristics of State Patterns
Definition of State mode: For a stateful object, complex "judgment logic" is extracted into different State objects, allowing the State object to change its behavior when its internal State changes.
The state mode is an object behavior mode with the following main advantages.
- Clearly structured, the state pattern localizes state-related behavior into a state and separates behavior from state to state to satisfy the Single Responsibility Principle.
- Display state transitions to reduce object interdependencies. Introducing different states into separate objects makes state transitions clearer and reduces object dependencies.
- The state class has clear responsibilities and is conducive to the expansion of the program. It is easy to add new states and transitions by defining new subclasses.
The main drawbacks of state mode are as follows.
- The use of state patterns will inevitably increase the number of classes and objects in the system.
- The structure and implementation of state modes are complex, and improper use can lead to confusion in program structure and code.
- The state mode does not support the open and close principle very well. For the state mode that can switch states, adding new state classes requires modifying those source codes responsible for state transition. Otherwise, it is impossible to switch to the new state, and modifying the behavior of a state class also requires modifying the source code of the corresponding class.
Structure and implementation of 22.2 state mode
State mode wraps the behavior of an object subject to environmental change in different state objects, with the intent that when an object's internal state changes, its behavior also changes. Now let's analyze its basic structure and implementation.
22.2. 1 State mode structure
The state mode contains the following main roles.
- Context role: Also known as context, it defines the interface required by the client, maintains a current state internally, and is responsible for switching specific states.
- Abstract State role: Defines an interface that encapsulates the behavior corresponding to a particular state in an environment object and can have one or more behaviors.
- Concrete State role: Implement the behavior corresponding to the abstract state and switch states if needed.
The structure diagram is shown in Figure 1.
22.2. Implementation of 2-state mode
The implementation code for the state mode is as follows:
public class StatePatternClient { public static void main(String[] args) { Context context = new Context(); //Create environment context.Handle(); //Processing Request context.Handle(); context.Handle(); context.Handle(); } } //Environment Class class Context { private State state; //Define the initial state of the environment class public Context() { this.state = new ConcreteStateA(); } //Set new state public void setState(State state) { this.state = state; } //Read Status public State getState() { return (state); } //Processing requests public void Handle() { state.Handle(this); } } //Abstract state class abstract class State { public abstract void Handle(Context context); } //Specific state class A class ConcreteStateA extends State { public void Handle(Context context) { System.out.println("The current state is A."); context.setState(new ConcreteStateB()); } } //Specific State Class B class ConcreteStateB extends State { public void Handle(Context context) { System.out.println("The current state is B."); context.setState(new ConcreteStateA()); } }
The program runs as follows:
The current state is A.
The current state is B.
The current state is A.
The current state is B.
Application examples of 22.3 state mode
[Example 1] Design a status conversion program of students'achievements with the state mode.
Analysis: This example contains three states: Failed, Medium and Excellent. When the student's score is less than 60, it is Failed. When the score is greater than or equal to 60 and less than 90, it is Medium. When the score is greater than or equal to 90, it is Excellent. We use the status mode to implement this program.
First, define an abstract state class that contains environment properties, state name properties, and current score properties, plus or minus addScore(intx), and checkState(), an abstract method that checks the current state.
Next, define the Fail state class LowState, the Medium state class MiddleState, and the Excellent state class HighState, which are concrete state classes that implement the checkState() method and are responsible for checking their own state and transforming it as appropriate.
Finally, the ScoreContext is defined, which contains the current status object and the add(int score) method of subtraction, through which the client class changes the status of the results. Figure 2 shows its structure.
The program code is as follows:
public class ScoreStateTest { public static void main(String[] args) { ScoreContext account = new ScoreContext(); System.out.println("Student Achievement Status Test:"); account.add(30); account.add(40); account.add(25); account.add(-15); account.add(-25); } } //Environment Class class ScoreContext { private AbstractState state; ScoreContext() { state = new LowState(this); } public void setState(AbstractState state) { this.state = state; } public AbstractState getState() { return state; } public void add(int score) { state.addScore(score); } } //Abstract state class abstract class AbstractState { protected ScoreContext hj; //Environmental Science protected String stateName; //Status Name protected int score; //Fraction public abstract void checkState(); //Check current status public void addScore(int x) { score += x; System.out.print("Add:" + x + "Score,\t Current score:" + score); checkState(); System.out.println("Score,\t Current status:" + hj.getState().stateName); } } //Specific Status Class: Fail class LowState extends AbstractState { public LowState(ScoreContext h) { hj = h; stateName = "Fail"; score = 0; } public LowState(AbstractState state) { hj = state.hj; stateName = "Fail"; score = state.score; } public void checkState() { if (score >= 90) { hj.setState(new HighState(this)); } else if (score >= 60) { hj.setState(new MiddleState(this)); } } } //Specific Status Class: Medium class MiddleState extends AbstractState { public MiddleState(AbstractState state) { hj = state.hj; stateName = "secondary"; score = state.score; } public void checkState() { if (score < 60) { hj.setState(new LowState(this)); } else if (score >= 90) { hj.setState(new HighState(this)); } } } //Specific Status Class: Excellent class HighState extends AbstractState { public HighState(AbstractState state) { hj = state.hj; stateName = "excellent"; score = state.score; } public void checkState() { if (score < 60) { hj.setState(new LowState(this)); } else if (score < 90) { hj.setState(new MiddleState(this)); } } }
The program runs as follows:
Student Achievement Status Test:
Plus: 30, current score: 30, current status: fail
Plus: 40, current score: 70, current status: medium
Plus: 25, current score: 95, current status: excellent
Plus: -15, current score: 80, current status: medium
Add: -25, current score: 55, current status: fail
[Example 2] Design a multithreaded state converter using State Mode.
Analysis: There are five states for multithreading, namely, new state, ready state, running state, blocking state and death state. Each state will be converted to another state when it encounters related method calls or event triggers. The state transition rule is shown in Figure 3.
Now define an abstract state class (TheadState), and then design a specific state class for each state shown in Figure 3. These are the New, Runnable, Running, Blocked, and Dead states, each of which has a method to trigger their transition state, the Environment class Mr. ThreadContext generates an initial state (New) and provides triggering methods. Figure 4 shows the structure of the thread state converter.
The program code is as follows:
public class ScoreStateTest { public static void main(String[] args) { ThreadContext context = new ThreadContext(); context.start(); context.getCPU(); context.suspend(); context.resume(); context.getCPU(); context.stop(); } } //Environment Class class ThreadContext { private ThreadState state; ThreadContext() { state = new New(); } public void setState(ThreadState state) { this.state = state; } public ThreadState getState() { return state; } public void start() { ((New) state).start(this); } public void getCPU() { ((Runnable) state).getCPU(this); } public void suspend() { ((Running) state).suspend(this); } public void stop() { ((Running) state).stop(this); } public void resume() { ((Blocked) state).resume(this); } } //Abstract state class: thread state abstract class ThreadState { protected String stateName; //Status Name } //Specific state class: New state class New extends ThreadState { public New() { stateName = "New Status"; System.out.println("Current thread is in: New state."); } public void start(ThreadContext hj) { System.out.print("call start()Method-->"); if (stateName.equals("New Status")) { hj.setState(new Runnable()); } else { System.out.println("The current thread is not in a new state and cannot be called start()Method."); } } } //Specific state class: Ready state class Runnable extends ThreadState { public Runnable() { stateName = "Ready State"; System.out.println("The current thread is: ready."); } public void getCPU(ThreadContext hj) { System.out.print("Get CPU time-->"); if (stateName.equals("Ready State")) { hj.setState(new Running()); } else { System.out.println("The current thread is not ready to be fetched CPU."); } } } //Specific Status Class: Running Status class Running extends ThreadState { public Running() { stateName = "running state"; System.out.println("Current thread is in: running state."); } public void suspend(ThreadContext hj) { System.out.print("call suspend()Method-->"); if (stateName.equals("running state")) { hj.setState(new Blocked()); } else { System.out.println("The current thread is not running and cannot be called suspend()Method."); } } public void stop(ThreadContext hj) { System.out.print("call stop()Method-->"); if (stateName.equals("running state")) { hj.setState(new Dead()); } else { System.out.println("The current thread is not running and cannot be called stop()Method."); } } } //Specific state class: blocking state class Blocked extends ThreadState { public Blocked() { stateName = "Blocking state"; System.out.println("The current thread is: blocked."); } public void resume(ThreadContext hj) { System.out.print("call resume()Method-->"); if (stateName.equals("Blocking state")) { hj.setState(new Runnable()); } else { System.out.println("The current thread is not blocked and cannot be called resume()Method."); } } } //Specific Status Class: Death Status class Dead extends ThreadState { public Dead() { stateName = "Death status"; System.out.println("Current thread is in: dead state."); } }
The program runs as follows:
The current thread is in the new state.
Call the start() method --> The current thread is: ready.
Get CPU Time --> Current Thread in: Running State.
Call suspend() method --> Current thread is in: blocked state.
Call resume() method --> Current thread is in: ready state.
Get CPU Time --> Current Thread in: Running State.
Call stop() method --> Current thread is in: dead state.
Scenarios for 22.4 State Mode
Usually you can consider using state mode in the following situations.
- Consider using state mode when an object's behavior depends on its state and it must change its behavior at runtime based on state.
- An operation has a large branch structure, and these branches are determined by the state of the object.
Extension of 22.5 state mode
In some cases, there may be multiple environment objects that need to share a set of states, which requires the introduction of an enjoyment mode where these specific state objects are placed in a collection for program sharing, and their structure is illustrated in Figure 5.
Analysis: The difference between the shared state mode is that a HashMap is added to the environment class to hold the relevant state, which can be obtained when a state is needed, and its program code is as follows:
package state; import java.util.HashMap; public class FlyweightStatePattern { public static void main(String[] args) { ShareContext context = new ShareContext(); //Create environment context.Handle(); //Processing Request context.Handle(); context.Handle(); context.Handle(); } } //Environment Class class ShareContext { private ShareState state; private HashMap<String, ShareState> stateSet = new HashMap<String, ShareState>(); public ShareContext() { state = new ConcreteState1(); stateSet.put("1", state); state = new ConcreteState2(); stateSet.put("2", state); state = getState("1"); } //Set new state public void setState(ShareState state) { this.state = state; } //Read Status public ShareState getState(String key) { ShareState s = (ShareState) stateSet.get(key); return s; } //Processing requests public void Handle() { state.Handle(this); } } //Abstract state class abstract class ShareState { public abstract void Handle(ShareContext context); } //Specific State Class 1 class ConcreteState1 extends ShareState { public void Handle(ShareContext context) { System.out.println("The current state is: State 1"); context.setState(context.getState("2")); } } //Specific State Class 2 class ConcreteState2 extends ShareState { public void Handle(ShareContext context) { System.out.println("The current state is: State 2"); context.setState(context.getState("1")); } }
The program runs as follows:
The current state is: State 1
The current state is: State 2
The current state is: State 1
The current state is: State 2
The Difference between Status Mode and Responsibility Chain Mode
Both state mode and responsibility chain mode eliminate the problem of if-else branching too much. However, in some cases, the state in the state mode can be interpreted as responsibility, and in this case, both modes can be used.
By definition, state mode emphasizes a change in the internal state of an object, while responsibility chain mode emphasizes a change between external node objects.
From the point of view of code implementation, the biggest difference between them is that each state object of the state mode knows its next state object to enter, while the responsibility chain mode does not know its next node handles the object, because the client is responsible for chain assembly.
Differences between state mode and policy mode
The UML class diagram architecture of the state and policy modes is almost identical, but the scenarios for the two are different. The policy mode can satisfy all kinds of arithmetic behaviors, which are independent of each other. The user can change the policy algorithm by himself. However, there is a relationship between the states of the state mode, and the effect of switching state automatically exists under certain conditions. The user can not specify the state, and can only set the initial state.
22.6 My State Mode Practice
Abstract state class:
package com.xjc.factory.state; //Abstract state class public interface State { abstract void handle(); }
Specific Status Class: Happy
package com.xjc.factory.state; //Specific Status Class 1: Happy State public class Happy implements State{ @Override public void handle() { System.out.println("Happy state, serious study"); } }
Specific Status Class 2: Sad State
package com.xjc.factory.state; //Specific Status Class 2: Sad State public class Sad implements State{ @Override public void handle() { System.out.println("Sad state, don't want to study"); } }
Environment class: human
package com.xjc.factory.state; //Environment class: human public class Person { //People have states private State state; //Construct to give a default state when a person is created, otherwise the null pointer problem will occur public Person() { this.state=new Happy(); } //Set state by construction method public Person(State state) { this.state = state; } public State getState() { return state; } //Set state by set method public void setState(State state) { this.state = state; } //To process a request, a handle is a person's action, which is determined by the current state public void Handle() { state.handle(); } }
Client:
package com.xjc.factory.state; public class Client { public static void main(String[] args) { //Create a person Person person = new Person(); //The direct caller's action, when the person's state is the default: happy state person.Handle(); //Set a person's state as sad person.setState(new Sad()); //Re-caller's action person.Handle(); } }
Run result:
23. Detailed Observer Mode
In the real world, many objects do not exist independently. A change in the behavior of one object may lead to a change in the behavior of one or more other objects. For example, when the price of a certain commodity rises, some businesses will be happy and consumers will be sad. Also, when we drive to an intersection, we stop at a red light and go at a green light. There are many other examples, such as stock prices and shareholders, WeChat public numbers and WeChat subscribers, weather forecasts and listeners from meteorological offices, thieves and police, etc.
The same is true in the software world, for example, the relationship between data in Excel and line charts, pie charts, and column charts. The relationship between model and view in MVC mode; Event source and event handler in event model. All of this is very convenient if you use the observer mode.
23.1 Definition and characteristics of observer mode
Definition of Observer mode: A one-to-many dependency among multiple objects in which all dependent objects are notified and automatically updated when the state of an object changes. This mode is sometimes referred to as Publish-Subscribe mode, Model-View mode, and it is an object behavior mode.
The observer mode is an object behavior mode with the following main advantages.
- The coupling relationship between the target and the observer is reduced, which is abstract. Compliance with the principle of dependency inversion.
- A trigger mechanism is established between the target and the observer.
Its main drawbacks are as follows.
- The dependency between the target and the observer is not completely broken, and circular references may occur.
- When there are many observer objects, the publication of notifications takes a lot of time, which affects the efficiency of the program.
23.2 Structure and Implementation of Observer Mode
When implementing the observer mode, it is important to note that there is no direct call between the specific target object and the specific observer object, otherwise they will be tightly coupled, which violates the object-oriented design principles.
23.2. 1 Structure of observer mode
The main roles of the observer mode are as follows.
- Subject role: Also known as abstract target class, it provides an abstract method for saving aggregated classes of observer objects, adding and deleting observer objects, and notifying all observers.
- Concrete Subject role: Also known as the concrete target class, it implements the notification method in the abstract target to notify all registered observer objects when the internal state of the specific subject changes.
- The Observer role: It is an abstract class or interface that contains an abstract method to update itself and is invoked when notified of changes to a specific topic.
- Specific Observer role: Implement the abstract method defined in the abstract observer to update its own state when notified of changes to the target.
The structure diagram of the observer mode is shown in Figure 1.
23.2. 2 Realization of observer mode
The implementation code for the observer mode is as follows:
package net.biancheng.c.observer; import java.util.*; public class ObserverPattern { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Observer obs1 = new ConcreteObserver1(); Observer obs2 = new ConcreteObserver2(); subject.add(obs1); subject.add(obs2); subject.notifyObserver(); } } //Abstract Target abstract class Subject { protected List<Observer> observers = new ArrayList<Observer>(); //Increase Observer Method public void add(Observer observer) { observers.add(observer); } //Delete Observer Method public void remove(Observer observer) { observers.remove(observer); } public abstract void notifyObserver(); //Notify Observer Method } //Specific objectives class ConcreteSubject extends Subject { public void notifyObserver() { System.out.println("Target change..."); System.out.println("--------------"); for (Object obs : observers) { ((Observer) obs).response(); } } } //Abstract observer interface Observer { void response(); //reaction } //Specific Observer 1 class ConcreteObserver1 implements Observer { public void response() { System.out.println("Specific observer 1 responds!"); } } //Specific Observer 1 class ConcreteObserver2 implements Observer { public void response() { System.out.println("Specific observer 2 responds!"); } }
The program runs as follows:
Target change...
Specific observer 1 responds!
Specific observer 2 responds!
23.3 Examples of application of observer mode
[Example 1] Using the observer model, a program is designed to analyze the impact of the appreciation or depreciation of the Renminbi exchange rate on the cost of imported products of an importing company or the income of export products of an exporting company, as well as the company's profit margin.
Analysis: When the "Renminbi exchange rate" rises, the cost of imported products of import companies decreases and the profit margin increases, while the revenue of export products of export companies decreases and the profit margin decreases. When the "Renminbi exchange rate" depreciates, the cost of imported products of import companies increases and the profit margin decreases, while the revenue of export products of export companies increases and the profit margin increases.
The Rate class here is an abstract target class that contains methods to save the List of observers (Company), increase/delete observers, and change(int number) the abstract method of exchange rate change; The Renminbi Exchange Rate (RMBrate) class is the specific target, it achieves the parent change(int number) method, that is, when the exchange rate of Renminbi changes through the relevant companies; The Company class is an abstract observer that defines an abstract method response(int number) for exchange rate reactions. Import Company and Export Company classes are specific observer classes that implement the parent class's response(int number) method, which reacts accordingly when they receive notifications of exchange rate changes. Figure 2 shows its structure.
The program code is as follows:
package net.biancheng.c.observer; import java.util.*; public class RMBrateTest { public static void main(String[] args) { Rate rate = new RMBrate(); Company watcher1 = new ImportCompany(); Company watcher2 = new ExportCompany(); rate.add(watcher1); rate.add(watcher2); rate.change(10); rate.change(-9); } } //Abstract Target: Exchange Rate abstract class Rate { protected List<Company> companys = new ArrayList<Company>(); //Increase Observer Method public void add(Company company) { companys.add(company); } //Delete Observer Method public void remove(Company company) { companys.remove(company); } public abstract void change(int number); } //Specific objectives: Renminbi exchange rate class RMBrate extends Rate { public void change(int number) { for (Company obs : companys) { ((Company) obs).response(number); } } } //Abstract Observer: Company interface Company { void response(int number); } //Specific Observer 1: Import Company class ImportCompany implements Company { public void response(int number) { if (number > 0) { System.out.println("Renminbi exchange rate appreciation" + number + "Base point, which reduces the cost of imported products and increases the profit margin of importing companies."); } else if (number < 0) { System.out.println("Devaluation of Renminbi Exchange Rate" + (-number) + "Base point, which increases the cost of imported products and reduces the profit margin of importing companies."); } } } //Specific Observer 2: Export Company class ExportCompany implements Company { public void response(int number) { if (number > 0) { System.out.println("Renminbi exchange rate appreciation" + number + "Base point, which reduces the revenue of export products and the profit margin of export companies."); } else if (number < 0) { System.out.println("Devaluation of Renminbi Exchange Rate" + (-number) + "Base point, which increases the revenue of export products and the profit margin of export companies."); } } }
The program runs as follows:
The appreciation of the RMB exchange rate by 10 basis points has reduced the cost of imported products and increased the profit margin of importing companies.
The appreciation of the Renminbi exchange rate by 10 basis points has reduced the revenue of export products and the profit margin of export companies.
The depreciation of the Renminbi exchange rate by 9 basis points has increased the cost of imported products and reduced the profit margin of importing companies.
The depreciation of the Renminbi exchange rate by 9 basis points has increased the revenue of export products and the profit margin of export companies.
The most frequently used observer mode in software development is event handling in form programming. All components in the form are "event sources", that is, target objects, while the objects of event handler classes are specific observer objects. The following is an example of an event handler for a school bell to illustrate how the Event Handling Model works in Windows.
[Example 2] An event handler for school ringtones was designed using observer mode.
Analysis: In this example, school bells are the source and target of events, teachers and students are event listeners and observers, and ringtones are event classes. When students and teachers come to the school's teaching area, they will pay attention to the school bell, which is called event binding. When the class time or the end time is up, the ringtone will be triggered and a "ringtone" event will be generated. When students and teachers hear the ringtone, they start or end classes, which is called event handling. This example is well suited for the observer mode implementation, and the event model for the school bell is shown in Figure 3.
This event processing model is now implemented in the Observer mode.
First, a RingEvent class is defined that records the type of ringtone (class ringtone/after-class ringtone).
Define a school bell (BellEventSource) class, which is an event source and an observer target class. It contains a listener container listener, which binds listeners (students or teachers), and has a way to generate ringtone events and notify all listeners.
Then, define the BellEventListener class, which is an abstract observer and contains the ringtone event handling method heardBell (RingEvent).
Finally, the TeachEventListener and the StuEventListener are defined, which are event listeners and specific observers who attend or leave classes when the ringtone is heard. Figure 4 shows the structure of the event handler for school ringtones.
The program code is as follows:
package net.biancheng.c.observer; import java.util.*; public class BellEventTest { public static void main(String[] args) { BellEventSource bell = new BellEventSource(); //Bell (Event Source) bell.addPersonListener(new TeachEventListener()); //Register monitor (teacher) bell.addPersonListener(new StuEventListener()); //Registration Monitor (Student) bell.ring(true); //Ring Class Ring System.out.println("------------"); bell.ring(false); //Bell for class } } //Ringtone event class: used to encapsulate event sources and some event-related parameters class RingEvent extends EventObject { private static final long serialVersionUID = 1L; private boolean sound; //true means the class bell and false means the class bell. public RingEvent(Object source, boolean sound) { super(source); this.sound = sound; } public void setSound(boolean sound) { this.sound = sound; } public boolean getSound() { return this.sound; } } //Target class: Event source, Bell class BellEventSource { private List<BellEventListener> listener; //Listener Container public BellEventSource() { listener = new ArrayList<BellEventListener>(); } //Bind listeners to event sources public void addPersonListener(BellEventListener ren) { listener.add(ren); } //Event trigger: rings the bell to trigger an event when the value of the ringtone sound changes. public void ring(boolean sound) { String type = sound ? "Class bell" : "recess bell"; System.out.println(type + "Sound!"); RingEvent event = new RingEvent(this, sound); notifies(event); //Notify all listeners registered on this event source } //Notifies all listeners bound to the event source to respond when an event occurs (invokes event handling methods) protected void notifies(RingEvent e) { BellEventListener ren = null; Iterator<BellEventListener> iterator = listener.iterator(); while (iterator.hasNext()) { ren = iterator.next(); ren.heardBell(e); } } } //Abstract Observer Class: Ringtone Event Listener interface BellEventListener extends EventListener { //Event handling, ringtone heard public void heardBell(RingEvent e); } //Specific Observer Class: Teacher Event Monitor class TeachEventListener implements BellEventListener { public void heardBell(RingEvent e) { if (e.getSound()) { System.out.println("The teacher is in class..."); } else { System.out.println("The teacher is over..."); } } } //Specific Observer Class: Student Event Monitor class StuEventListener implements BellEventListener { public void heardBell(RingEvent e) { if (e.getSound()) { System.out.println("Classmates, class is over..."); } else { System.out.println("Classmates, class is over..."); } } }
The program runs as follows:
Class bell rings!
The teacher is in class...
Classmates, class is over...
The bell rings after class!
The teacher is over...
Classmates, class is over...
23.4 Scenarios for the Observer Mode
In software systems, when one party's behavior depends on the other party's behavior changes, the observer mode can be used to loosely couple the two parties so that one party's change can be notified to the other party of interest and the other party can respond to it.
From the previous analysis and application examples, it is known that the observer pattern is suitable for the following situations.
- There is a one-to-many relationship between objects, and changes in the state of one object can affect other objects.
- When an abstract model has two aspects, one of which depends on the other, it can be encapsulated in separate objects so that they can be independently changed and reused.
- To achieve a function similar to that of a broadcasting mechanism, you do not need to know the specific listener. You only need to distribute the broadcasting, and the objects of interest in the system will receive it automatically.
- Multilevel nested use forms a chain trigger mechanism that enables events to be notified across two observer types.
23.5 Extension of Observer Mode
In Java, through java.util.Observable class and java. Util. The Observer interface defines observer modes, and you can write observer mode instances as long as you implement their subclasses.
- Observable class
The Observable class is an abstract target class that has a Vector vector to hold all observer objects to be notified. Here are three of its most important methods.
void addObserver(Observer o) method: Used to add a new observer object to a vector.
void notifyObservers(Object arg) method: Calls the update() method of all observer objects in a vector to notify them of data changes. Often, the later a vector is added, the earlier the observer is notified.
void setChange() method: Used to set an internal flag bit of boolean type indicating that the target object has changed. When it is true, notifyObservers() notifies the observer. - Observer interface
The Observer interface is an abstract observer that monitors changes in the target object. When the target object changes, the observer is notified and the void update(Observable o,Object arg) method is called to do the appropriate work.
[Example 3] An example of observer mode for crude oil futures is implemented using Observable class and Observer interface.
Analysis: When the price of crude oil rises, the empty side is sad and prosperous in many situations. When the oil price fell, the empty market thrived and many people were sad. The Abstract target (Observable) class in this example is defined in Java and can directly define its subclass, the OilFutures class, which defines a SetPriCe(float price) method that notifies all observers when the crude oil data changes by calling the notifyObservers(Object arg) method of its parent class. In addition, the abstract observer interface (Observer) in this example has already been defined in Java, as long as its subclasses are defined, that is, specific observer classes (including multiple classes of Bull s and empty square classes of Bear) and the update(Observable o,Object arg) method is implemented. Figure 5 shows its structure.
The program code is as follows:
package net.biancheng.c.observer; import java.util.Observer; import java.util.Observable; public class CrudeOilFutures { public static void main(String[] args) { OilFutures oil = new OilFutures(); Observer bull = new Bull(); //Multi-party Observer bear = new Bear(); //Empty side oil.addObserver(bull); oil.addObserver(bear); oil.setPrice(10); oil.setPrice(-8); } } //Specific target class: crude oil futures class OilFutures extends Observable { private float price; public float getPrice() { return this.price; } public void setPrice(float price) { super.setChanged(); //Set internal flags to indicate changes in data super.notifyObservers(price); //Notify the observer that the price has changed this.price = price; } } //Specific observer class: multi-party class Bull implements Observer { public void update(Observable o, Object arg) { Float price = ((Float) arg).floatValue(); if (price > 0) { System.out.println("Oil Price Rise" + price + "Yuan, many people are happy!"); } else { System.out.println("Falling oil prices" + (-price) + "Yuan, many people are sad!"); } } } //Specific observer class: empty side class Bear implements Observer { public void update(Observable o, Object arg) { Float price = ((Float) arg).floatValue(); if (price > 0) { System.out.println("Oil Price Rise" + price + "Yuan, empty side is sad!"); } else { System.out.println("Falling oil prices" + (-price) + "Yuan, empty side is happy!"); } } }
The program runs as follows:
Oil price rises 10.0 yuan, empty side is sad!
Oil price rises 10.0 yuan, many people are happy!
The oil price has dropped 8.0 yuan, so the empty side is happy!
Oil price fell 8.0 yuan, many people are sad!
23.6 My Observer Mode Practice
Abstract Observing Target:
package com.xjc.factory.observer; import java.util.ArrayList; import java.util.List; //Abstract observation target public abstract class ObserverTarget { //Observer List for Observed Objects protected List<Observer> observers = new ArrayList<>(); //Add an observer public void addObs(Observer observer) { observers.add(observer); } //Remove Observer public void remove(Observer observer) { observers.remove(observer); } //Notify observers public abstract void NotifyObserver(double price); }
Specific objectives: stocks
package com.xjc.factory.observer; //Specific objectives: stocks public class Stock extends ObserverTarget{ //Price of stock private double price=1; //Stock price changes public void addPrice(double price) { this.price += price; //Call method to notify changes after changes NotifyObserver(price); } @Override public void NotifyObserver(double price) { System.out.println("Specific observation objectives-->Stocks, change"); for (Observer o : observers) { o.response(price); } } }
Abstract observer:
package com.xjc.factory.observer; //Abstract observer public interface Observer { //The observer's response to the change void response(double price); }
Specific observers: shareholders
package com.xjc.factory.observer; //Specific observers: shareholders public class Stockholders implements Observer { //Shareholders have names private String name; //Give Shareholders Name by Constructive Method public Stockholders(String name) { this.name = name; } @Override public void response(double price) { if (price > 0) { System.out.println("The stock has risen:" + price + "Yuan, Shareholders" + name + "Happy!!!"); } else if (price < 0) { System.out.println("The stock has fallen:" + price + "Yuan, Shareholders" + name + "It's too sad!!!"); } else { System.out.println("The stock did not fall or rise." + name + "No waves in my heart!!!"); } } }
Client:
package com.xjc.factory.observer; public class Client { public static void main(String[] args) { //Create a stock Stock stock = new Stock(); //Create two shareholders Stockholders S1 = new Stockholders("Zhang San"); Stockholders S2 = new Stockholders("Li Si"); //Stockholders Watch Stocks stock.addObs(S1); stock.addObs(S2); //Stock price changes three times stock.addPrice(1); stock.addPrice(-1); stock.addPrice(0); } }
Run result:
24. Intermediator Model (Detailed Edition)
In real life, there are often complex interactions among many objects. This interaction is often a "mesh structure", which requires each object to know what it needs to interact with. For example, everyone must remember the phone calls of all his or her friends; and if someone in a friend's phone changes, he or she must have all the other friends modify it together, which is called "pull the trigger and move the whole body", which is very complex.
Changing this "mesh structure" to a "star structure" will greatly reduce the "coupling" between them, so just look for an "intermediary". As mentioned earlier, the problem of "Everyone must remember all friends'phone calls" can be solved by establishing an "address book" on the Internet that every friend can access. There are many other examples, such as when you are just working and want to rent a house, you can find a "housing agency"; Or, if you've just found a job in a new city, you can ask for help from the Talent Exchange Center.
There are many examples of this during software development, such as in the MVC framework. Controller (C) is the mediator of model (M) and view (V), and the mediator of QQ chat program is the QQ server. All of these can be achieved by using the mediator mode, which will greatly reduce the coupling between objects and improve the flexibility of the system.
Definition and characteristics of the mediator pattern in 24.1
Definition of Mediator mode: Defines a mediator object to encapsulate the interaction between a series of objects, to loosen the coupling between the original objects, and to independently change the interaction between them. Mediator mode, also known as mediation mode, is a typical application of Dimitt's rule.
The intermediary model is an object behavior model with the following main advantages.
- Classes perform their duties in accordance with Dimitt's Law.
- This reduces the coupling between objects, making them easy to reuse independently.
- Turn one-to-many associations among objects into one-to-one associations, improve the flexibility of the system, and make the system easy to maintain and expand.
The main disadvantage is that the mediator pattern transforms the direct interdependence of multiple objects into the dependency of the mediator and multiple colleague classes. The more colleagues there are, the heavier the intermediary becomes, complex and difficult to maintain.
Structure and implementation of 24.2 mediator mode
The key to the implementation of the mediator pattern is to find out the "mediator". The structure and implementation of the mediator pattern are analyzed below.
24.2. 1 The structure of the mediator pattern
The mediator pattern contains the following main roles.
- Abstract Mediator role: It is the interface of the mediator and provides an abstract method for registering and forwarding colleague object information.
- Specific Mediator Role: Implements the mediator interface, defines a List to manage colleague objects, coordinates interactions between various colleague roles, and therefore relies on the colleague role.
- Colleague role: Define the interface of the colleague class, save the mediator object, provide an abstract method for the interaction of the colleague object, and implement the common functions of all the interacting colleague classes.
- Specific Colleague role: is the implementer of an abstract colleague class, and the mediator object is responsible for subsequent interactions when interaction with other colleague objects is required.
The structure diagram of the mediator pattern is shown in Figure 1.
24.2. 2 Implementation of Intermediator Mode
The implementation code for the mediator pattern is as follows:
package net.biancheng.c.mediator; import java.util.*; public class MediatorPattern { public static void main(String[] args) { Mediator md = new ConcreteMediator(); Colleague c1, c2; c1 = new ConcreteColleague1(); c2 = new ConcreteColleague2(); md.register(c1); md.register(c2); c1.send(); System.out.println("-------------"); c2.send(); } } //Abstract Mediator abstract class Mediator { public abstract void register(Colleague colleague); public abstract void relay(Colleague cl); //Forward } //Specific intermediaries class ConcreteMediator extends Mediator { private List<Colleague> colleagues = new ArrayList<Colleague>(); public void register(Colleague colleague) { if (!colleagues.contains(colleague)) { colleagues.add(colleague); colleague.setMedium(this); } } public void relay(Colleague cl) { for (Colleague ob : colleagues) { if (!ob.equals(cl)) { ((Colleague) ob).receive(); } } } } //Abstract Colleague Class abstract class Colleague { protected Mediator mediator; public void setMedium(Mediator mediator) { this.mediator = mediator; } public abstract void receive(); public abstract void send(); } //Specific colleague class class ConcreteColleague1 extends Colleague { public void receive() { System.out.println("Specific colleague class 1 receives the request."); } public void send() { System.out.println("Specific colleague class 1 makes the request."); mediator.relay(this); //Ask intermediary to forward } } //Specific colleague class class ConcreteColleague2 extends Colleague { public void receive() { System.out.println("Specific colleague class 2 received the request."); } public void send() { System.out.println("Specific colleague class 2 makes the request."); mediator.relay(this); //Ask intermediary to forward } }
The results of the program are as follows:
Specific colleague class 1 makes the request.
Specific colleague class 2 received the request.
Specific colleague class 2 makes the request.
Specific colleague class 1 receives the request.
Application examples of mediator mode in 24.3
[Example 1] Write a program of "Shaoguan Real Estate Exchange Platform" using the intermediary mode.
Description: Shaoguan Real Estate Exchange Platform is a platform provided by Real Estate Intermediary Company for information exchange between Seller's Customer and Buyer's Customer. It is more suitable to use the intermediary mode.
First, define a Medium interface, which is an abstract mediator that contains the customer registration method register(Customer member) and the information forwarding method relay(String from,String ad); Define a Shaoguan real estate intermediary (EstateMedium) company, which is a specific intermediary class. It contains List objects to store customer information, and implements an abstract method in the intermediary company.
Then, define a Customer class, which is an abstract colleague class that contains the object of the mediator and the interface between the send(String ad) method that sends information and the receive(String from, String ad) method that receives information. Since this program is a form program, this class inherits the JPmme class. ActionPerformed (ActionEvent) is used to handle action events.
Finally, the Seller and Buyer classes, which are specific colleague classes and subclasses of the Customer class, are defined. They implement abstract methods in the parent class, communicate information through the intermediary class, and their structure diagram is shown in Figure 2.
The program code is as follows:
package net.biancheng.c.mediator; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; public class DatingPlatform { public static void main(String[] args) { Medium md = new EstateMedium(); //Real estate agency Customer member1, member2; member1 = new Seller("Zhang San(Seller)"); member2 = new Buyer("Li Si(Buyer)"); md.register(member1); //Customer registration md.register(member2); } } //Abstract intermediary: intermediary company interface Medium { void register(Customer member); //Customer registration void relay(String from, String ad); //Forward } //Specific intermediaries: real estate intermediaries class EstateMedium implements Medium { private List<Customer> members = new ArrayList<Customer>(); public void register(Customer member) { if (!members.contains(member)) { members.add(member); member.setMedium(this); } } public void relay(String from, String ad) { for (Customer ob : members) { String name = ob.getName(); if (!name.equals(from)) { ((Customer) ob).receive(from, ad); } } } } //Abstract Colleague Class: Customer abstract class Customer extends JFrame implements ActionListener { private static final long serialVersionUID = -7219939540794786080L; protected Medium medium; protected String name; JTextField SentText; JTextArea ReceiveArea; public Customer(String name) { super(name); this.name = name; } void ClientWindow(int x, int y) { Container cp; JScrollPane sp; JPanel p1, p2; cp = this.getContentPane(); SentText = new JTextField(18); ReceiveArea = new JTextArea(10, 18); ReceiveArea.setEditable(false); p1 = new JPanel(); p1.setBorder(BorderFactory.createTitledBorder("Received content:")); p1.add(ReceiveArea); sp = new JScrollPane(p1); cp.add(sp, BorderLayout.NORTH); p2 = new JPanel(); p2.setBorder(BorderFactory.createTitledBorder("Send content:")); p2.add(SentText); cp.add(p2, BorderLayout.SOUTH); SentText.addActionListener(this); this.setLocation(x, y); this.setSize(250, 330); this.setResizable(false); //Window size is not adjustable this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public void actionPerformed(ActionEvent e) { String tempInfo = SentText.getText().trim(); SentText.setText(""); this.send(tempInfo); } public String getName() { return name; } public void setMedium(Medium medium) { this.medium = medium; } public abstract void send(String ad); public abstract void receive(String from, String ad); } //Specific colleague class: Seller class Seller extends Customer { private static final long serialVersionUID = -1443076716629516027L; public Seller(String name) { super(name); ClientWindow(50, 100); } public void send(String ad) { ReceiveArea.append("I(Seller)say: " + ad + "\n"); //Scroll the scrollbar to the bottom ReceiveArea.setCaretPosition(ReceiveArea.getText().length()); medium.relay(name, ad); } public void receive(String from, String ad) { ReceiveArea.append(from + "say: " + ad + "\n"); //Scroll the scrollbar to the bottom ReceiveArea.setCaretPosition(ReceiveArea.getText().length()); } } //Specific colleague class: Buyer class Buyer extends Customer { private static final long serialVersionUID = -474879276076308825L; public Buyer(String name) { super(name); ClientWindow(350, 100); } public void send(String ad) { ReceiveArea.append("I(Buyer)say: " + ad + "\n"); //Scroll the scrollbar to the bottom ReceiveArea.setCaretPosition(ReceiveArea.getText().length()); medium.relay(name, ad); } public void receive(String from, String ad) { ReceiveArea.append(from + "say: " + ad + "\n"); //Scroll the scrollbar to the bottom ReceiveArea.setCaretPosition(ReceiveArea.getText().length()); } }
The results of the program are shown in Figure 3.
Scenarios for 24.4 mediator mode
The structure and characteristics of the mediator model are analyzed before, and the following scenarios are analyzed below.
- When complex network structure relationships exist between objects, resulting in confused and difficult reuse of dependencies.
- When you want to create an object that runs between classes, but you don't want to generate new subclasses.
Extension of 24.5 Intermediator Mode
In practical development, there are usually two ways to simplify the mediator model to make development easier.
The mediator interface is not defined and the specific mediator object implementation is singleton.
Colleague objects do not hold intermediaries, but instead get them directly and call them when needed.
Figure 4 shows the structure of a simplified mediator pattern.
The program code is as follows:
package net.biancheng.c.mediator; import java.util.*; public class SimpleMediatorPattern { public static void main(String[] args) { SimpleColleague c1, c2; c1 = new SimpleConcreteColleague1(); c2 = new SimpleConcreteColleague2(); c1.send(); System.out.println("-----------------"); c2.send(); } } //Simple Single Intermediator class SimpleMediator { private static SimpleMediator smd = new SimpleMediator(); private List<SimpleColleague> colleagues = new ArrayList<SimpleColleague>(); private SimpleMediator() { } public static SimpleMediator getMedium() { return (smd); } public void register(SimpleColleague colleague) { if (!colleagues.contains(colleague)) { colleagues.add(colleague); } } public void relay(SimpleColleague scl) { for (SimpleColleague ob : colleagues) { if (!ob.equals(scl)) { ((SimpleColleague) ob).receive(); } } } } //Abstract Colleague Class interface SimpleColleague { void receive(); void send(); } //Specific colleague class class SimpleConcreteColleague1 implements SimpleColleague { SimpleConcreteColleague1() { SimpleMediator smd = SimpleMediator.getMedium(); smd.register(this); } public void receive() { System.out.println("Specific colleague class 1: Receive the request."); } public void send() { SimpleMediator smd = SimpleMediator.getMedium(); System.out.println("Specific colleague class 1: Make a request..."); smd.relay(this); //Ask intermediary to forward } } //Specific colleague class class SimpleConcreteColleague2 implements SimpleColleague { SimpleConcreteColleague2() { SimpleMediator smd = SimpleMediator.getMedium(); smd.register(this); } public void receive() { System.out.println("Specific colleague class 2: Receive the request."); } public void send() { SimpleMediator smd = SimpleMediator.getMedium(); System.out.println("Specific colleague class 2: Make a request..."); smd.relay(this); //Ask intermediary to forward } }
The program runs as follows:
Specific colleague class 1: Make a request...
Specific colleague class 2: Receive the request.
Specific colleague class 2: Make a request...
Specific colleague class 1: Receive the request.
24.6 My Intermediator Model Practice
Abstract intermediaries:
package com.xjc.mediator; //Abstract Mediator public interface MarriageAgency { void register(Person person); void pair(Person person); }
Specific intermediaries: Marriage agencies:
package com.xjc.mediator; import java.util.ArrayList; import java.util.List; //Specific intermediaries: marriage agencies public class MarriageAgencyImpl implements MarriageAgency { //Young men and women in a matchmaking agency List<Person> people = new ArrayList<>(); //Registration of young men and women @Override public void register(Person person) { people.add(person); } //Match Object @Override public void pair(Person person) { for (Person p : people) { System.out.print("Encountered:"+p.name+"====="); if (p.age == person.requestAge && p.sex != person.sex) { System.out.println("Age, sex, into the cave!!!"); } else if (p.age != person.requestAge && p.sex != person.sex) { System.out.println("A little age difference,Gender difference, barely into the cave!!!"); } else { if (p == person) { System.out.print("This is me, not happy."); }else { System.out.print("Sex is the same. That's not happy."); } System.out.println(person.name+"!!!"); } } } }
Colleague class (participant class):
package com.xjc.mediator; //Colleague class (participant class) public class Person { String name; int age; String sex; int requestAge; MarriageAgency agency; //Construction Method Assignment public Person(String name, int age, String sex, int requestAge, MarriageAgency agency) { this.name = name; this.age = age; this.sex = sex; this.requestAge = requestAge; this.agency = agency; //Register colleagues to intermediaries agency.register(this); } //Find a partner public void findPartner() { agency.pair(this); } }
Client:
package com.xjc.mediator; //Client public class Client { public static void main(String[] args) { //Create an intermediary MarriageAgencyImpl marriageAgency = new MarriageAgencyImpl(); //Create a participant (colleague) and register with the intermediary Person giao mulberry = new Person("giao mulberry", 30, "male", 30, marriageAgency); Person Dili Reba = new Person("Dili Reba", 20, "female", 18, marriageAgency); Person Yang Mi = new Person("Yang Mi", 21, "female", 19, marriageAgency); Person Mr. Guo = new Person("Mr. Guo", 30, "female", 18, marriageAgency); Person Old Eight = new Person("Old Eight", 30, "male", 18, marriageAgency); //Find a partner giao mulberry.findPartner(); System.out.println(); Dili Reba.findPartner(); } }
Run result:
25. Iterator Mode (Detailed Edition)
In real life and program design, it is often necessary to access each element of an aggregated object, such as chain table traversal in "data structure". Generally, the creation and traversal of a chain table are placed in the same class, but this method is not conducive to the expansion of the program. If you want to change the traversal method, you must modify the program source code, which violates the "open-close principle".
Since it is not advisable to encapsulate traversal methods in aggregate classes, is it possible to implement them by users themselves without providing them in aggregate classes? The answer is the same, because there are two drawbacks to this approach:
Exposing the internal representation of aggregate classes makes their data unsafe;
Increased customer burden.
Iterator mode overcomes these shortcomings by inserting an iterator between the client access class and the aggregate class, which separates the aggregate object from its traversal behavior, hides its internal details from the client, and satisfies the Single Responsibility Principle and the Open and Close Principle, such as Collection, List, Set, Map in Java.
Iterator mode is widely used in life, such as: conveyor belts in the logistics system, no matter what items are transferred, will be packed into one box with a unified two-dimensional code. This way, we don't need to care about what's in the box. We just need to check the destinations one by one when distributing. For example, when we ride on vehicles in normal times, we always brush our cards or brush our faces to enter the station. We don't need to care about information such as male or female, disabled or normal people.
Definition and characteristics of 25.1 iterator pattern
Definition of an Iterator pattern: Provides an object to sequentially access a series of data in an aggregated object without exposing the internal representation of the aggregated object.
Iterator mode is an object behavior mode with the following main advantages.
- Access the contents of an aggregated object without exposing its internal representation.
- The traversal task is left to the iterator, which simplifies the aggregation classes.
- It supports traversing an aggregate in different ways, and it can even customize subclasses of iterators to support new traversals.
- It is easy to add new aggregate and iterator classes without modifying the original code.
- Good encapsulation provides a unified interface for traversing different aggregation structures.
The main disadvantage is that the number of classes is increased, which to some extent increases the complexity of the system.
In daily development, we hardly write iterators on our own. Unless you need to customize an iterator for your own implemented data structure, the API s provided by the open source framework are sufficient.
Structure and implementation of 25.2 iterator mode
The iterator pattern is achieved by separating the traversal behavior of aggregated objects and abstracting them into iterator classes to allow external code to transparently access the aggregated internal data without exposing the internal structure of the aggregated objects. Now let's analyze its basic structure and implementation.
25.2. 1 Iterator pattern structure
The iterator pattern mainly includes the following roles.
- The Aggregate role: defines interfaces for storing, adding, deleting aggregated objects, and creating iterator objects.
- ConcreteAggregate role: Implements an abstract aggregate class and returns an instance of a specific iterator.
- The role of an abstract Iterator: Defines an interface for accessing and traversing aggregate elements, usually including hasNext(), first(), next().
- Concretelterator role: Implements the methods defined in the abstract iterator interface, completes the traversal of aggregated objects, and records the current location of the traversal.
The structure diagram is shown in Figure 1.
25.2. 2 Iterator pattern implementation
The implementation code for the iterator pattern is as follows:
package net.biancheng.c.iterator; import java.util.*; public class IteratorPattern { public static void main(String[] args) { Aggregate ag = new ConcreteAggregate(); ag.add("Sun Yat-sen University"); ag.add("South China Polytechnic"); ag.add("shaoguan university"); System.out.print("The aggregated content is:"); Iterator it = ag.getIterator(); while (it.hasNext()) { Object ob = it.next(); System.out.print(ob.toString() + "\t"); } Object ob = it.first(); System.out.println("\nFirst: " + ob.toString()); } } //Abstract aggregation interface Aggregate { public void add(Object obj); public void remove(Object obj); public Iterator getIterator(); } //Specific aggregation class ConcreteAggregate implements Aggregate { private List<Object> list = new ArrayList<Object>(); public void add(Object obj) { list.add(obj); } public void remove(Object obj) { list.remove(obj); } public Iterator getIterator() { return (new ConcreteIterator(list)); } } //Abstract Iterator interface Iterator { Object first(); Object next(); boolean hasNext(); } //Specific iterators class ConcreteIterator implements Iterator { private List<Object> list = null; private int index = -1; public ConcreteIterator(List<Object> list) { this.list = list; } public boolean hasNext() { if (index < list.size() - 1) { return true; } else { return false; } } public Object first() { index = 0; Object obj = list.get(index); ; return obj; } public Object next() { Object obj = null; if (this.hasNext()) { obj = list.get(++index); } return obj; } }
The program runs as follows:
Aggregated contents are: South China Institute of Technology Shaoguan University, Sun Yat-sen University
First: Sun Yat-sen University
Application examples of 25.3 iterator pattern
[Example 1] Write a program to browse Wuyuan tourism scenery with iterator mode.
Analysis: There are many scenic spots and historical sites in Wuyuan. To design a program to view the related scenic spots pictures (click here to download the scenic spots pictures shown in this example) and an introduction, it is more appropriate to use the "iterator mode" design.
First, design a WyViewSpot class to hold the name and introduction of each picture, and then design a ViewSpotSet interface, which is an abstract aggregation class that provides a way to add and delete Wuyuan attractions, as well as a way to get iterators.
Then, define a WyViewSpotSet class, which is a concrete aggregation class that uses ArrayList to store all the attraction information and implement the abstract method in the parent class, and define the ViewSpotltemtor interface of Wuyuan attractions, which contains related methods to view the attraction information.
Last, The WyViewSpotlterator class, which defines Wuyuan attractions, implements the abstract method of the parent class; the client program is designed as a window program, which initializes the data in the ViewSpotSet and implements the ActionListener interface, which views Wuyuan attractions through the ViewSpotlterator. (WyViewSpot). Figure 2 shows its structure.
The program code is as follows:
package net.biancheng.c.iterator; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; public class PictureIterator { public static void main(String[] args) { new PictureFrame(); } } //Photoframe Class class PictureFrame extends JFrame implements ActionListener { private static final long serialVersionUID = 1L; ViewSpotSet ag; //Wuyuan scenic spot collection interface ViewSpotIterator it; //Wuyuan attraction iterator interface WyViewSpot ob; //Wuyuan scenic spots PictureFrame() { super("Part of Wuyuan, China's Most Beautiful Country"); this.setResizable(false); ag = new WyViewSpotSet(); ag.add(new WyViewSpot("River Bay", "Jiangwan Scenic Area is a country of Wuyuan 5 A A large number of ancient buildings, such as Xiaojiang Ancestral Temple, Yongsi Street, Tengjia Old House, Wuyuan People's Home, Xianyuan Township, Baigong Workshop, are exquisite and exquisite.")); ag.add(new WyViewSpot("Li Keng", "Li Keng Village is an ancient village with Li's surname as its main inhabitant. It is the fourth village of the country. A Class I tourist scenic spot, with its unique architectural style, is a well-known emblem building, giving a quiet and peaceful feeling.")); ag.add(new WyViewSpot("Sixi Yancun", "Situated in Sikou Town, Wuyuan County, Sixi Yancun was founded in the fifth year (1199) of Qingyuan, Southern Song Dynasty. At that time, the village builder Yu Shi was named after Siqingxi Water.")); ag.add(new WyViewSpot("Xiaoqi Village", "Knowing the reputation of "the first village of Chinese tea culture" and "national Eco-Demonstration village", most of the houses in the village are Qing Dynasty buildings with different styles, small and medium alleys in the village are paved with blue stones, twists and turns, and loops like a chess board.")); ag.add(new WyViewSpot("Ju Jing Village", "Ju Jing village is shaped around mountains and waters, with small rivers forming a large semi-circle around the village for nearly a week, surrounded by alpine mountains. It conforms to the design of China's gossip "Back Mountain Front Water". Local people call it "Facebasin Village".")); ag.add(new WyViewSpot("Daoling", "Shaoling is a well-known place where the "sun autumn" culture originated and an ancient village of Huizhou with a history of nearly 600 years. Daoling is a typical mountain village, where the residential buildings are distributed in fan-shaped trapezoidal pattern around the water inlet.")); ag.add(new WyViewSpot("Bifrost", "Rainbow Bridge is a unique roofed bridge in Wuyuan. It not only has a beautiful shape, but also can be used for pedestrians to rest in rainy days. Its name is derived from Tang poem "Two water glasses, two bridges fall into the rainbow".")); ag.add(new WyViewSpot("Wolong Valley", "Wolong Valley is National 4 A Class tourist area, where the flying spring waterfall spills silver and emerald, the colorful pool and deep pool are green and fresh, and the mountain rocks are upright and exquisite, bringing out a natural ink-splashing landscape painting.")); it = ag.getIterator(); //Get Wuyuan Attractions Iterator ob = it.first(); this.showPicture(ob.getName(), ob.getIntroduce()); } //display picture void showPicture(String Name, String Introduce) { Container cp = this.getContentPane(); JPanel picturePanel = new JPanel(); JPanel controlPanel = new JPanel(); String FileName = "src/iterator/Picture/" + Name + ".jpg"; JLabel lb = new JLabel(Name, new ImageIcon(FileName), JLabel.CENTER); JTextArea ta = new JTextArea(Introduce); lb.setHorizontalTextPosition(JLabel.CENTER); lb.setVerticalTextPosition(JLabel.TOP); lb.setFont(new Font("Song Style", Font.BOLD, 20)); ta.setLineWrap(true); ta.setEditable(false); //ta.setBackground(Color.orange); picturePanel.setLayout(new BorderLayout(5, 5)); picturePanel.add("Center", lb); picturePanel.add("South", ta); JButton first, last, next, previous; first = new JButton("First"); next = new JButton("Next"); previous = new JButton("Previous"); last = new JButton("Last"); first.addActionListener(this); next.addActionListener(this); previous.addActionListener(this); last.addActionListener(this); controlPanel.add(first); controlPanel.add(next); controlPanel.add(previous); controlPanel.add(last); cp.add("Center", picturePanel); cp.add("South", controlPanel); this.setSize(630, 550); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } @Override public void actionPerformed(ActionEvent arg0) { String command = arg0.getActionCommand(); if (command.equals("First")) { ob = it.first(); this.showPicture(ob.getName(), ob.getIntroduce()); } else if (command.equals("Next")) { ob = it.next(); this.showPicture(ob.getName(), ob.getIntroduce()); } else if (command.equals("Previous")) { ob = it.previous(); this.showPicture(ob.getName(), ob.getIntroduce()); } else if (command.equals("Last")) { ob = it.last(); this.showPicture(ob.getName(), ob.getIntroduce()); } } } //Wuyuan scenic spots class WyViewSpot { private String Name; private String Introduce; WyViewSpot(String Name, String Introduce) { this.Name = Name; this.Introduce = Introduce; } public String getName() { return Name; } public String getIntroduce() { return Introduce; } } //Abstract Aggregation: Wuyuan Attraction Set Interface interface ViewSpotSet { void add(WyViewSpot obj); void remove(WyViewSpot obj); ViewSpotIterator getIterator(); } //Concrete Aggregation: Wuyuan scenic spot collection class WyViewSpotSet implements ViewSpotSet { private ArrayList<WyViewSpot> list = new ArrayList<WyViewSpot>(); public void add(WyViewSpot obj) { list.add(obj); } public void remove(WyViewSpot obj) { list.remove(obj); } public ViewSpotIterator getIterator() { return (new WyViewSpotIterator(list)); } } //Abstract Iterator: Wuyuan Attraction Iterator Interface interface ViewSpotIterator { boolean hasNext(); WyViewSpot first(); WyViewSpot next(); WyViewSpot previous(); WyViewSpot last(); } //Specific iterator: Wuyuan scenic spot iterator class WyViewSpotIterator implements ViewSpotIterator { private ArrayList<WyViewSpot> list = null; private int index = -1; WyViewSpot obj = null; public WyViewSpotIterator(ArrayList<WyViewSpot> list) { this.list = list; } public boolean hasNext() { if (index < list.size() - 1) { return true; } else { return false; } } public WyViewSpot first() { index = 0; obj = list.get(index); return obj; } public WyViewSpot next() { if (this.hasNext()) { obj = list.get(++index); } return obj; } public WyViewSpot previous() { if (index > 0) { obj = list.get(--index); } return obj; } public WyViewSpot last() { index = list.size() - 1; obj = list.get(index); return obj; } }
The result of running the program is shown in Figure 3.
Scenarios for the 25.4 iterator pattern
The structure and features of the iterator pattern are described earlier, and the scenarios for its application are described below. The iterator pattern is commonly used in the following situations.
- When multiple traversals are required for aggregated objects.
- When a unified interface is required to traverse different aggregation structures.
- A representation that accesses the contents of an aggregated object without exposing its internal details.
Since aggregation is closely related to iterators, most languages provide iterator classes when implementing aggregate classes, so in most cases, using iterators for aggregate classes already in the language is sufficient.
Extensions to the 25.5 iterator pattern
Iterator modes are often used in conjunction with combinational modes, and iterators are often hidden in container component classes of combinational modes when accessing container components in combinational modes. Of course, an external iterator can also be constructed to access the container components as shown in Figure 4.
25.6 My Iterator Mode Practice
Abstract aggregation:
package com.xjc.iterator; //Abstract aggregation public interface Aggregate { void add(Object object); void remove(Object object); Iterator getIterator(); }
Specific aggregation:
package com.xjc.iterator; import java.util.ArrayList; import java.util.List; //Specific aggregation public class ConcreteAggregate implements Aggregate{ private List<Object> list = new ArrayList<>(); @Override public void add(Object object) { list.add(object); } @Override public void remove(Object object) { list.remove(object); } @Override public Iterator getIterator() { return new ConcreteIterator(list); } }
Abstract Iterator:
package com.xjc.iterator; //Abstract Iterator public interface Iterator { Object first(); Object next(); boolean hasNext(); }
Specific iterators:
package com.xjc.iterator; import java.util.List; //Specific iterators public class ConcreteIterator implements Iterator { private List<Object> list = null; private int index = -1; public ConcreteIterator(List<Object> list) { this.list = list; } @Override public Object first() { /* * The example code here is: * index = 0; Object obj = list.get(index); Writing like this is wrong. Writing like this calls the first method before executing hasNext will cause an error. The first element cannot be found * */ Object obj = list.get(0); return obj; } @Override public Object next() { Object obj=null; if (this.hasNext()) { obj = list.get(++index); } return obj; } @Override public boolean hasNext() { if (index < list.size() - 1) { return true; } return false; } }
Use:
package com.xjc.iterator; import com.xjc.state.Person; public class IteratorPattern { public static void main(String[] args) { ConcreteAggregate ca = new ConcreteAggregate(); ca.add(123); ca.add("abc"); ca.add(new Person()); int[] ints = {1}; ca.add(ints); System.out.println("The aggregated content is:"); Iterator it = ca.getIterator(); System.out.println("First:"+it.first()); while (it.hasNext()) { Object obj=it.next(); System.out.println(obj.toString()); } /*My iterator has flaws, such as hasNext going through a while loop and returning false, which is not a difficult problem to solve. However, you can see from this question that==>Since aggregation is closely related to iterators, most languages provide iterator classes when implementing aggregation classes. So for the most part, it's sufficient to use iterators for aggregation classes already in the language.*/ System.out.println(it.hasNext()); while