A design pattern is a series of solutions to a specific problem developed by many software developers through long periods of trial and error and application. When introducing design patterns in some current textbooks, some cases are difficult to understand because they are separated from the actual application scenarios, and some are exaggerated because the scenarios are simple. This paper will explain the application of three common design patterns in the form of “teacher-student dialogue” in a humorous way, combining the experience in the design and development of Meituan financial service platform and practical cases. Hope to improve the system design ability of the students to help or inspire.

The introduction

M: In the world of programmers, a teacher and a mentor are talking.

“Teacher, I feel that my code is very inelegant when I write code recently. Is there any way to optimize it?”

“Well, consider using the textbook to systematically implement clean code from annotations, naming, methods, and exceptions.”

“However, I would like to say that my code conforms to various coding specifications, but the implementation always feels not clean enough and always needs to be changed!” Student Xiaoming sighed.

The teacher looked at Xiao Ming’s code and said, “I see. This is a flaw in the system design. It’s not abstract enough, it’s not readable enough, it’s not robust enough.”

“Yes, yes, yes, yes, yes, yes, yes, yes, yes, yes, yes.” Xiao Ming asked impatiently.

The teacher knocked xiao Ming’s head: “Don’t be too impetuous, there is no way to make you immediately become a system design expert. But for your question, I think design patterns can help.”

“Design patterns?” Xiao Ming doesn’t understand.

“Yes.” The teacher nodded, “There is no way in the world. When more people walk, it becomes a way. In the world of programmers, there are no design patterns. As many people write code, they have developed a set of patterns that improve the efficiency of development and maintenance. They are called design patterns. “A design pattern is not a dogma or paradigm, it is a universal and reusable solution in a particular scenario, a best practice that can be used to improve code readability, extensibility, maintainability, and testability.”

“Oh, I see. How am I supposed to learn?”

“No hurry, I’m going to walk you through design patterns.”

Reward delivery strategy

The first day, the teacher asked Xiao Ming, “Do you know anything about event marketing?”

“I know that. Event marketing refers to enterprises’ participation in existing activities with high social attention, or the integration of effective resources to independently plan large-scale activities, so as to rapidly improve the visibility, reputation and influence of enterprises and their brands, such as lucky draw and red envelope.”

The teacher nodded, “Yes. Let’s say we’re doing a marketing campaign that requires users to participate in a campaign, complete a series of tasks, and get some reward in return. The rewards of the activity include meituan takeout, wine tour and food vouchers, and now we need your help to design a reward distribution scheme.”

Because of similar development experience before, Xiaoming got the demand and began to write the code without saying anything else:

// Reward service
class RewardService {
    // External services
    private WaimaiService waimaiService;
    private HotelService hotelService;
    private FoodService foodService;
    // use the conditional judgment on the entry to award prizes
    public void issueReward(String rewardType, Object ... params) {
        if ("Waimai".equals(rewardType)) {
            WaimaiRequest request = new WaimaiRequest();
            // Build the input parameter
            request.setWaimaiReq(params);
            waimaiService.issueWaimai(request);
        } else if ("Hotel".equals(rewardType)) {
            HotelRequest request = new HotelRequest();
            request.addHotelReq(params);
            hotelService.sendPrize(request);
        } else if ("Food".equals(rewardType)) {
            FoodRequest request = new FoodRequest(params);
            foodService.getCoupon(request);
        } else {
          	throw new IllegalArgumentException("rewardType error!"); }}}Copy the code

Xiao Ming wrote the Demo quickly and sent it to the teacher.

“If we’re going to introduce new vouchers, does that mean you have to change that part of the code?” “Asked the teacher.

Ming was stunned for a moment, but the teacher asked again: “If the coupon issuing interface of Meituan takeout is changed or replaced later, does this logic have to be modified simultaneously?”

Xiao Ming was lost in thought and could not answer for a while.

The experienced teacher pointed out the problem of this design: “There are two main problems with your code. One is that it does not conform to the open and close principle. It can be foreseen that if category coupons are added in the future, the trunk code needs to be directly modified, and we advocate that the code should be closed to modification. The second is that it does not comply with Demeter’s law, and the prize logic is highly coupled with the various downstream interfaces. As a result, the change of the interface will directly affect the organization of the code, making the code less maintainable.”

Xiao Ming suddenly realized: “Then, if I abstract each function interacting with the downstream interface into a single service, encapsulate its parameter assembly and exception handling, so that the main logic of awarding prizes can be decoupled from it, will it be more scalable and maintainable?”

“That’s a good idea. As I told you about design patterns, this example can be optimized using policy patterns and adapter patterns.”

Xiao Ming took the opportunity to learn these two design patterns. The first is the strategic pattern:

The policy pattern [1-5] defines a series of algorithms and encapsulates each algorithm so that they are interchangeable. Policy patterns typically contain the following roles:

  • Abstract Strategy class: Defines a common interface that is implemented in different ways by different algorithms, and is used by environment actors to invoke different algorithms, typically implemented using interfaces or abstract classes.
  • Concrete Strategy class: Interfaces that implement abstract policy definitions and provide Concrete algorithm implementations.
  • Context class: Holds a reference to a policy class that is ultimately called by the client.

Then there is the adapter pattern:

Adapter pattern [1-5] : Converts the interface of one class into another interface that the customer expects, making classes that would otherwise not work together due to interface incompatibations work together. The adapter pattern contains the following key roles:

  • Target interface: The interface expected by the current system service. It can be an abstract class or interface.

  • Adaptee class: It is a component interface in an existing component library that is accessed and adapted.

  • Adapter class: It is a converter that converts the Adapter interface into the target interface by inheriting or referencing the Adapter’s object, allowing customers to access the Adapter in the format of the target interface.

Combining with the optimization idea, Xiaoming first designed the policy interface, and adapted each downstream interface class into the policy class through the idea of adapter:

// Policy interface
interface Strategy {
    void issue(Object ... params);
}
// Take-out strategy
class Waimai implements Strategy {
  	private WaimaiService waimaiService;
    @Override
    public void issue(Object... params) {
        WaimaiRequest request = new WaimaiRequest();
        // Build the input parameterrequest.setWaimaiReq(params); waimaiService.issueWaimai(request); }}// Wine travel strategy
class Hotel implements Strategy {
  	private HotelService hotelService;
    @Override
    public void issue(Object... params) {
        HotelRequest request = newHotelRequest(); request.addHotelReq(params); hotelService.sendPrize(request); }}// Food strategy
class Food implements Strategy {
  	private FoodService foodService;
    @Override
    public void issue(Object... params) {
        FoodRequest request = newFoodRequest(params); foodService.payCoupon(request); }}Copy the code

Ming then creates the environment class for the policy pattern and calls it to the reward service:

// Use branching to determine the obtained policy context
class StrategyContext {
    public static Strategy getStrategy(String rewardType) {
        switch (rewardType) {
            case "Waimai":
                return new Waimai();
            case "Hotel":
                return new Hotel();
            case "Food":
                return new Food();
            default:
                throw new IllegalArgumentException("rewardType error!"); }}}// Optimized policy service
class RewardService {
    public void issueReward(String rewardType, Object ... params) { Strategy strategy = StrategyContext.getStrategy(rewardType); strategy.issue(params); }}Copy the code

After the optimization of Xiaoming’s code, although the structure and design are more complex than before, but considering the robustness and scalability, it is very worthwhile.

“Look, is my optimized version perfect?” Xiao Ming said complacently.

“Coupling is down, but you can do better.”

“How?” Xiao Ming is a little confused.

“I ask you, is the policy class a stateful model? If not, can we consider singleton?”

“That’s true.” Xiao Ming seems to understand.

“Also, the environment class’s fetching strategy method responsibilities are clear, but you’re still not completely closed to change.”

After the teacher’s guidance, Xiao Ming soon understood the key point: “Then I can singleton the policy class to reduce the cost, and realize the function of self-registration to completely solve the branch judgment.”

Xiaoming lists the main points of the singleton pattern:

The singleton pattern [1-5] design pattern is a creation pattern that provides an optimal way to create objects.

This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique objects directly, without instantiating the objects of the class.

Finally, Xiaoming uses a registry in the policy environment class to record the registration information of each policy class, and provides an interface for the policy class to call for registration. At the same time, hanhan-style singleton pattern is used to optimize the design of the policy class:

// Policy context, used to manage policy registration and acquisition
class StrategyContext {
    private static final Map<String, Strategy> registerMap = new HashMap<>();
    // Register policy
    public static void registerStrategy(String rewardType, Strategy strategy) {
        registerMap.putIfAbsent(rewardType, strategy);
    }
    // Get the policy
    public static Strategy getStrategy(String rewardType) {
        returnregisterMap.get(rewardType); }}// Abstract policy class
abstract class AbstractStrategy implements Strategy {
    // Class registration method
    public void register(a) {
        StrategyContext.registerStrategy(getClass().getSimpleName(), this); }}// Single exception selling strategy
class Waimai extends AbstractStrategy implements Strategy {
    private static final Waimai instance = new Waimai();
  	private WaimaiService waimaiService;
    private Waimai(a) {
        register();
    }
    public static Waimai getInstance(a) {
        return instance;
    }
    @Override
    public void issue(Object... params) {
        WaimaiRequest request = new WaimaiRequest();
        // Build the input parameterrequest.setWaimaiReq(params); waimaiService.issueWaimai(request); }}// Single case wine travel strategy
class Hotel extends AbstractStrategy implements Strategy {
  	private static final Hotel instance = new Hotel();
  	private HotelService hotelService;
    private Hotel(a) {
        register();
    }
    public static Hotel getInstance(a) {
        return instance;
    }
    @Override
    public void issue(Object... params) {
        HotelRequest request = newHotelRequest(); request.addHotelReq(params); hotelService.sendPrize(request); }}// Singleton gourmet strategy
class Food extends AbstractStrategy implements Strategy {
  	private static final Food instance = new Food();
  	private FoodService foodService;
    private Food(a) {
        register();
    }
    public static Food getInstance(a) {
        return instance;
    }
    @Override
    public void issue(Object... params) {
        FoodRequest request = newFoodRequest(params); foodService.payCoupon(request); }}Copy the code

Finally, the structure class diagram designed by Xiaoming is as follows:

If you use the Spring framework, you can also use Spring’s Bean mechanism to replace some of the above design. Singletons can be created and registered directly using the @Component and @postConstruct annotations. The code is much simpler.

At this point, after many discussions, reflection and optimization, Xiaoming finally got a design with low coupling and high cohesion and conforming to the open and closed principle.

“Teacher, I began to learn how to use design patterns to solve the problems I found. How did I do this time?”

“Qualified. Still, guard against arrogance and rashness.”

Design of task model

“Remember when I asked you to design a reward delivery strategy?” “Asked the teacher suddenly.

“Of course. A good design pattern can make the work twice as productive.” “Xiao Ming replied.

“Well, that would mention the event marketing component, which seems to include missions in addition to rewards.”

Ming nodded, and the teacher continued: “Now, I want you to finish the task model design. You need to focus on the flow of state changes and the notification of state changes.”

Xiao Ming gladly accepted the teacher’s difficult problem. He first defines a set of enumerations of task states and behaviors:

// Task status enumeration
@AllArgsConstructor
@Getter
enum TaskState {
    INIT("Initialize"),
    ONGOING( "In progress"),
    PAUSED("Suspended"),
    FINISHED("Done"),
    EXPIRED("Expired");private final String message;
}
// Behavior enumeration
@AllArgsConstructor
@Getter
enum ActionType {
    START(1."Start"),
    STOP(2."Pause"),
    ACHIEVE(3."Complete"),
    EXPIRE(4."Overdue");private final int code;
    private final String message;
}
Copy the code

Then, Xiao Ming began to write the state change function:

class Task {
    private Long taskId;
    // The default state of the task is initialization
    private TaskState state = TaskState.INIT;
    // Event services
    private ActivityService activityService;
    // Task manager
    private TaskManager taskManager;
    // Use conditional branches to update tasks
    public void updateState(ActionType actionType) {
        if (state == TaskState.INIT) {
            if(actionType == ActionType.START) { state = TaskState.ONGOING; }}else if (state == TaskState.ONGOING) {
            if (actionType == ActionType.ACHIEVE) {
                state = TaskState.FINISHED;
                // Notify the external service when the task is completed
                activityService.notifyFinished(taskId);
                taskManager.release(taskId);
            } else if (actionType == ActionType.STOP) {
                state = TaskState.PAUSED;
            } else if(actionType == ActionType.EXPIRE) { state = TaskState.EXPIRED; }}else if (state == TaskState.PAUSED) {
            if (actionType == ActionType.START) {
                state = TaskState.ONGOING;
            } else if(actionType == ActionType.EXPIRE) { state = TaskState.EXPIRED; }}}}Copy the code

In the above implementation, Ming accomplished two important functions in the updateState method:

  1. Receive different behaviors, and then update the status of the current task;
  2. Notify the activity and task manager to which the task belongs when the task expires.

Of course, as Ming’s system development skills and code quality awareness improved, he was able to recognize the flaws in this feature design.

“Teacher, my code is still not elegant as I said before.”

“Well, tell me yourself what the problem is.”

“First, the method uses conditional judgment to control statements, but when the conditions are complex or there are too many states, the conditional judgment statements will be too bloated, poor readability, not extensible, and difficult to maintain. Adding new states requires adding new if-else statements, which violates the open and close principle and is not conducive to program expansion.”

The teacher agreed, and Ming continued: “Second, the task class is not cohesive enough. It is aware of the models of other domains or modules, such as activity and task managers, in the notification implementation, which makes the code too coupled to expand.”

“Good,” said the teacher approvingly. “You are aware and able to find the code problems on your own. That is a big step.”

“How can we solve this problem?” Xiao Ming continued to ask.

“This can also be optimized through design patterns. First, state mode can be used for control of state flow, and second, observer mode can be used for notification of task completion.”

After receiving the instructions, Ming immediately went to learn the structure of the state pattern:

State mode [1-5] : For stateful objects, complex “judgment logic” is extracted into different state objects, allowing the state object to change its behavior when its internal state changes. The state pattern contains the following key 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 the specific state.

  • Abstract State role: Defines an interface that encapsulates the behavior of a particular State in an environment object, which can have one or more behaviors.

  • Concrete State role: Implements the behavior of the abstract State and switches the State if necessary.

According to the definition of the state mode, Xiaoming expands the TaskState enumeration class into several state classes and has the ability to complete the state flow. Then the implementation of the task class is optimized:

// Task status abstract interface
interface State {
    // The default implementation does nothing
    default void update(Task task, ActionType actionType) {
        // do nothing}}// Task initial state
class TaskInit implements State {
    @Override
    public void update(Task task, ActionType actionType) {
        if  (actionType == ActionType.START) {
            task.setState(newTaskOngoing()); }}}// The task is in progress
class TaskOngoing implements State {
    private ActivityService activityService;
    private TaskManager taskManager; 
    @Override
    public void update(Task task, ActionType actionType) {
        if (actionType == ActionType.ACHIEVE) {
            task.setState(new TaskFinished());
            / / notice
            activityService.notifyFinished(taskId);
            taskManager.release(taskId);
        } else if (actionType == ActionType.STOP) {
            task.setState(new TaskPaused());
        } else if (actionType == ActionType.EXPIRE) {
            task.setState(newTaskExpired()); }}}// The task is paused
class TaskPaused implements State {
    @Override
    public void update(Task task, ActionType actionType) {
        if (actionType == ActionType.START) {
            task.setState(new TaskOngoing());
        } else if (actionType == ActionType.EXPIRE) {
            task.setState(newTaskExpired()); }}}// Task completed status
class TaskFinished implements State {}// Task expired status
class TaskExpired implements State {}@Data
class Task {
    private Long taskId;
    // initialize to initial state
    private State state = new TaskInit();
    // Update the status
    public void updateState(ActionType actionType) {
        state.update(this, actionType); }}Copy the code

Ming was delighted to see that the coupling degree of the task class after processing by the state mode was reduced, which was in line with the open and closed principle. The advantage of the state mode is that it conforms to the principle of single responsibility, and the state class has clear responsibility, which is conducive to the expansion of the program. However, the cost of this design is that the number of state classes increases, so the more complex the state flow logic is and the more actions need to be processed, the more favorable the application of state mode is. In addition, the state class itself does not support the open closed principle well enough, and may need to be used with caution if the state flow logic changes frequently.

After processing the status, Xiao Ming uses the observer mode to optimize the notification of task completion according to the teacher’s guidance:

Observer mode [1-5] : A one-to-many dependency exists between multiple objects. When the state of an object changes, all dependent objects are notified and automatically updated. This pattern, sometimes referred to as publish-subscribe or model-view, is an object behavior pattern. The main roles of observer mode are as follows.

  • Abstract Subject role: Also called abstract target class, it provides an aggregation class for holding observer objects and methods for adding and deleting observer objects, as well as abstract methods for notifying all observers.

  • Concrete Subject role: Also known as Concrete Target class, it implements notification methods in abstract targets to notify all registered observer objects when the internal state of a Concrete Subject changes.

  • Abstract Observer role: It is an abstract class or interface that contains an abstract method that updates itself and is called when notified of changes to a specific topic.

  • Concrete Observer role: Implements an abstract method defined in an abstract Observer to update its own state when it is notified of changes to the target.

Ming first designed the abstract target and the abstract observer, and then made the receiving notification function of the activity and task manager into the concrete observer:

// Abstract the observer
interface Observer {
    void response(Long taskId); / / response
}
// Abstract target
abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();
    // Add observer method
    public void add(Observer observer) {
        observers.add(observer);
    }
    // Delete the observer method
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    // Notify the observer method
    public void notifyObserver(Long taskId) {
        for(Observer observer : observers) { observer.response(taskId); }}}// Active observer
class ActivityObserver implements Observer {
    private ActivityService activityService;
    @Override
    public void response(Long taskId) { activityService.notifyFinished(taskId); }}// Task management observer
class TaskManageObserver implements Observer {
    private TaskManager taskManager;
    @Override
    public void response(Long taskId) { taskManager.release(taskId); }}Copy the code

Finally, Xiaoming optimized the state class of the task into a general notification method, and defined the observers required by the task state when the task initial state executed the state flow:

// The task is in progress
class TaskOngoing extends Subject implements State {  
    @Override
    public void update(Task task, ActionType actionType) {
        if (actionType == ActionType.ACHIEVE) {
            task.setState(new TaskFinished());
            / / notice
            notifyObserver(task.getTaskId());
        } else if (actionType == ActionType.STOP) {
            task.setState(new TaskPaused());
        } else if (actionType == ActionType.EXPIRE) {
            task.setState(newTaskExpired()); }}}// Task initial state
class TaskInit implements State {
    @Override
    public void update(Task task, ActionType actionType) {
        if  (actionType == ActionType.START) {
            TaskOngoing taskOngoing = new TaskOngoing();
            taskOngoing.add(new ActivityObserver());
            taskOngoing.add(newTaskManageObserver()); task.setState(taskOngoing); }}}Copy the code

Finally, the structure class diagram designed by Xiaoming is as follows:

Through the observer pattern, Ming loosens the coupling between the task state and the notifier (in fact, the observer pattern is not fully decoupled yet, and if you want to further decouple, you can consider learning and using the publit-subscribe pattern, which will not be described here).

So far, Xiao Ming has successfully used the state pattern to design the whole state machine implementation of the task with high cohesion, high expansibility and single responsibility, as well as the task state change notification method that achieves loose coupling and complies with the dependency inversion principle.

“Teacher, I gradually realized the design flaws of the code and learned to make optimization with more complex design patterns.”

“Yes, keep up the good work!

Iterative refactoring of activities

“Xiao Ming, I have a new task this time.” The teacher appears in front of Xiao Ming who is reading Design Patterns carefully.

“Ok. It just so happens that I’ve learned how design patterns work, and I can finally use them.”

“Before you designed and developed the activity model, now we need to add a layer of risk control to our engagement approach to task-based activities.”

“OK. I wanted to take the opportunity to recreate the design.”

The activity model is characterized by a large number of components. Xiao Ming’s original activity model was constructed in the following way:

// Abstract the active interface
interface ActivityInterface {
  	void participate(Long userId);
}
/ / action classes
class Activity implements ActivityInterface {
    private String type;
    private Long id;
    private String name;
    private Integer scene;
    private String material;
      
    public Activity(String type) {
        this.type = type;
        The build part of the ID depends on the type of the activity
        if ("period".equals(type)) {
            id = 0L; }}public Activity(String type, Long id) {
        this.type = type;
        this.id = id;
    }
    public Activity(String type, Long id, Integer scene) {
        this.type = type;
        this.id = id;
        this.scene = scene;
    }
    public Activity(String type, String name, Integer scene, String material) {
        this.type = type;
        this.scene = scene;
        this.material = material;
        // The construction of name depends entirely on the type of the activity
        if ("period".equals(type)) {
            this.id = 0L;
            this.name = "period" + name;
        } else {
            this.name = "normal"+ name; }}// Participate in activities
  	@Override
    public void participate(Long userId) {
        // do nothing}}// Task-based activities
class TaskActivity extends Activity {
    private Task task;
    public TaskActivity(String type, String name, Integer scene, String material, Task task) {
        super(type, name, scene, material);
        this.task = task;
    }
    // Participate in task-based activities
    @Override
    public void participate(Long userId) {
        // Update the task status to In progresstask.getState().update(task, ActionType.START); }}Copy the code

After independent analysis, Xiao Ming found that the structure of the activity was not reasonable enough, and the main problems were as follows:

  1. The large number of active constructors leads to too many constructors that can be combined, especially when the constructor needs to be modified as fields are added to the model.
  2. Some components are constructed in a certain order, but the current implementation does not reflect the order, resulting in a chaotic construction logic, and there is some duplicate code.

After discovering the problem, Ming recalled his learning results and immediately thought of using the Builder mode in the creation mode to do reconstruction:

The Builder pattern [1-5] refers to the separation of the construction of a complex object from its representation, so that the same construction process can create different representations. It takes a complex object and breaks it down into simple objects and builds it step by step. It separates change from immutability, that is, the components of a product are constant, but each part can be chosen flexibly. The main roles of builder mode are as follows:

  1. A Product role: It is a complex object with multiple components that are created by a specific builder.
  2. Abstract Builder: This is an interface that contains an abstract method for creating the various child parts of a product, usually including a method getResult() that returns a complex product.
  3. Concrete Builder: to realize the Builder interface and complete the Concrete creation method of each part of complex products.
  4. Director: It calls the component construction and assembly methods in the builder object to create a complex object. There is no product-specific information in the Director.

Each field of the above activity is a product, as defined by the Builder pattern. Thus, Ming can easily implement this by implementing a static constructor class in an activity:

/ / action classes
class Activity implements ActivityInterface {
    protected String type;
    protected Long id;
    protected String name;
    protected Integer scene;
    protected String material;
    // full parameter constructor
  	public Activity(String type, Long id, String name, Integer scene, String material) {
        this.type = type;
        this.id = id;
        this.name = name;
        this.scene = scene;
        this.material = material;
    }
    @Override
    public void participate(Long userId) {
        // do nothing
    }
    Static builder classes, using the singular recursive template pattern to allow inheritance and return inherited builder classes
    public static class Builder<T extends Builder<T>> {
        protected String type;
        protected Long id;
        protected String name;
        protected Integer scene;
        protected String material;
        public T setType(String type) {
            this.type = type;
            return (T) this;
        }
        public T setId(Long id) {
            this.id = id;
            return (T) this;
        }
        public T setId(a) {
            if ("period".equals(this.type)) {
                this.id = 0L;
            }
            return (T) this;
        }
        public T setScene(Integer scene) {
            this.scene = scene;
            return (T) this;
        }
        public T setMaterial(String material) {
            this.material = material;
            return (T) this;
        }
        public T setName(String name) {
            if ("period".equals(this.type)) {
                this.name = "period" + name;
            } else {
                this.name = "normal" + name;
            }
            return (T) this;
        }
        public Activity build(a){
            return newActivity(type, id, name, scene, material); }}}// Task-based activities
class TaskActivity extends Activity {
    protected Task task;
  	// full parameter constructor
    public TaskActivity(String type, Long id, String name, Integer scene, String material, Task task) {
        super(type, id, name, scene, material);
        this.task = task;
    }
  	// Participate in task-based activities
    @Override
    public void participate(Long userId) {
        // Update the task status to In progress
        task.getState().update(task, ActionType.START);
    }
    // Inherits the builder class
    public static class Builder extends Activity.Builder<Builder> {
        private Task task;
        public Builder setTask(Task task) {
            this.task = task;
            return this;
        }
        public TaskActivity build(a){
            return newTaskActivity(type, id, name, scene, material, task); }}}Copy the code

Ming found that the above builder did not use a complete implementation such as an abstract builder class, but basically completed the construction process of the various components of the activity. In builder mode, you build the field Types sequentially, then the other components sequentially, and then use the Build method to get the completed activity. On the one hand, this design has good encapsulation and separation of construction and presentation. On the other hand, the expansibility is good, and each specific builder is independent from each other, which is beneficial to the decoupling of the system. This is a valuable refactoring. In practice, if there are multiple field types and each field requires only a simple assignment, Lombok’s @Builder annotation can be used to implement a lightweight Builder.

After reconstructing the design of activity construction, Xiao Ming began to increase risk control over the method of participating in activities. The easiest way is definitely to modify the target method directly:

public void participate(Long userId) {
    // Perform risk control on the target user. If failure, throw an exception
    Risk.doControl(userId);
    // Update the task status to In progress
    task.state.update(task, ActionType.START);
}
Copy the code

However, considering that it is best to avoid direct modification of the old method as much as possible, and at the same time increase risk control for the method, it is also a relatively common new feature, which may be used in multiple places.

“Teacher, will risk control appear in the participation methods of multiple activities?”

“It’s a possibility. Some activities require risk control, others do not. Risk control seems to adorn participation in this approach when appropriate.”

“Oh, decorator mode!”

Ming immediately thought of using the decorator pattern to complete the design:

The decorator pattern [1-5] is defined as a pattern that dynamically adds some responsibilities (that is, additional functionality) to an object without changing its existing structure. It belongs to the object structure pattern. The decorator pattern mainly contains the following roles:

  1. Abstract Component Role: Defines an abstract interface to specify objects that are ready to receive additional responsibilities.
  2. ConcreteComponent roles: Implement abstract artifacts, adding responsibilities to them by decorating them.
  3. Decorator role: Inherits an abstract artifact and contains instances of a concrete artifact that can be subclassed to extend its functionality.
  4. The ConcreteDecorator role: Implements methods associated with abstract decorators and adds additional responsibilities to concrete component objects.

After Ming uses decorator mode, the new code looks like this:

// Abstract decorates the role
abstract class ActivityDecorator implements ActivityInterface {
    protected ActivityInterface activity;
    public ActivityDecorator(ActivityInterface activity) {
        this.activity = activity;
    }
    public abstract void participate(Long userId);
}
// Packaging classes that can control the risk of activities
class RiskControlDecorator extends ActivityDecorator {
    public RiskControlDecorator(ActivityInterface activity) {
        super(activity);
    }
    @Override
  	public void participate(Long userId) {
        // Perform risk control on the target user. If failure, throw an exception
      	Risk.doControl(userId);
        // Update the task status to In progressactivity.participate(userId); }}Copy the code

Finally, the structure class diagram designed by Xiaoming is as follows:

In the end, Ming completed the reconstruction and iteration of the activity model through his own thinking and analysis, combined with the design pattern knowledge he learned.

“Teacher, I have been able to independently analyze the functional characteristics, and reasonably apply the design mode to complete the program design and code reconstruction. Thank you very much.”

“It’s great that design patterns are a software design best practice that you already understand and apply to practice. But there is no end to learning, so we need to keep improving!”

conclusion

This paper takes three real scenes as the starting point, and with the help of two virtual characters, Xiao Ming and teacher, tries to describe the application scenarios, advantages and disadvantages of the design pattern in a humorous “dialogue” way. If you want to systematically understand design patterns, you can also learn through many textbooks on the market, which introduce the structure and implementation of the classic 23 design patterns. However, the content of many textbooks is sometimes confusing, even though it is accompanied by a large number of examples. The main reasons are: on the one hand, many cases are far from the actual application scenarios; On the other hand, some design patterns are clearly more suitable for large, complex structures, but when applied to simple scenarios, they seem to make the code more cumbersome and redundant. Therefore, this article aims to introduce design patterns in a more understandable way through this “dialogue + code presentation + structural class diagram” approach.

Of course, this paper only describes some of the more common design patterns, there are other design patterns, still need students to read classic works, draw inferences from one another, learn to apply. We also hope that more students can improve their ability in system design by learning design patterns.

The resources

  • [1] Gamma E. Design patterns: The foundation of Reusable object-oriented software [M]. China Machine Press, 2007.
  • [2] Freeman. Head First Design Mode [M]. China Electric Power Press, 2007.
  • [3] oodesign.com
  • [4] java-design-patterns.com
  • [5] Java Design Patterns: Comprehensive analysis of 23 design patterns

Author’s brief introduction

Jia Kai and Yang Liu, from meituan financial service platform/co-branded card r&d team.

Read more technical articles from meituan’s technical team

Front end | | algorithm back-end | | | data security operations | iOS | Android | test

| in the public bar menu dialog reply goodies for [2021], [2020] special purchases, goodies for [2019], [2018] special purchases, 【 2017 】 special purchases, such as keywords, to view Meituan technology team calendar year essay collection.

| this paper Meituan produced by the technical team, the copyright ownership Meituan. You are welcome to reprint or use the content of this article for non-commercial purposes such as sharing and communication. Please mark “Content reprinted from Meituan Technical team”. This article shall not be reproduced or used commercially without permission. For any commercial activity, please send an email to [email protected] for authorization.