First, the introduction-first, a good game
The legend of zelda-Wilderness Interest, a stand-alone RPG game released by Nintendo (the “folk expert”) on its own console platforms WIIU and SWITCH on March 3, 2017, is truly a “miracle” of the times. Nintendo, which is making “open” games for the first time, has a textbook definition of how such games should be made.
What really attracted me about this game was his details. For example, chestnut, the world in “Wilderness Breath” has two concepts of weather and temperature. It will rain and thunder, and there will be severe cold and hot summer. However, unlike most games, these days are just simple backgrounds and will actually affect every operation of the protagonist Link. For example, climbing a mountain in rainy days will slip. In thunder days, if you have metal equipment, you will be struck by lightning (wooden equipment is fine! ）； In severe cold, physical strength will slowly be lost (just put on a warm coat to solve the problem); If you use an explosive arrow in hot weather, it will explode in situ! Wait;
It is these details that make the game world very real and interesting.
Second, the problem-how to design such a game code?
As a Cheng Xuyuan, one cannot help thinking after playing games, how should such game codes be designed and written?
For example, “climbing” requires judging the climbing position, link’s equipment (some of which can make you climb faster), the weather at that time, link’s physical strength, and many other conditions. there must be countless if else involved. what’s more, this is only one of the simple operations, extending to all games, and its complexity is unimaginable.
Obviously such a design is not feasible.
Let’s assume that the “climbing” method only focuses on the climbing (if you have physical strength, you can succeed; otherwise, you fail). Other judgments are performed outside the method, such as judging weather, equipment, location, etc. This conforms to the principle of single responsibility and low coupling in program design, and the method of judging weather can be reused elsewhere, enhancing the reusability and testability of codes, which seems feasible!
How should such code be designed? This leads us to today’s protagonist-decorator model.
3. The protagonist-decorator model
According to GoF’s definition of decorator pattern in “Design Pattern: Basis of Reusable Object-Oriented Software” (hereinafter referred to as “Design Pattern”), decorator pattern is also called wrapper pattern. Its purpose is to expand the function of objects in a transparent way to users, and it is an alternative to inheritance.
To draw the focus together:
- Transparent to users: Generally, the external interface of the decorated object remains unchanged, and “climbing” is still “climbing” in any decoration.
- Extended object function: generally refers to modifying or adding object functions, such as link can ski with a shield in snow, while flat ground does not have this ability.
- An alternative to inheritance: students familiar with object-oriented must be familiar with inheritance. Here we focus on some shortcomings of inheritance itself: 1) subclasses and superclasses in inheritance have strong coupling, and the modification of superclasses will affect all subclasses; 2) superclass to subclass is “white-box reuse”. subclasses must understand all implementations of superclass, which destroys encapsulation. 3) When the project is huge, inheritance will make sub-categories explode. For example, there is a cooking system in “Wilderness’s Breath”. Any two kinds of food materials can be matched to produce a cooking. Assuming that 10 kinds of food materials can be used, 10*10=100 sub-categories will be constructed to represent the cooking results using inheritance method, while the decorator mode only uses 10+1=11 sub-categories to complete the above work. (It also includes the mixing of any kind of food. In fact, it is possible in the game. )
Finally, summarize the characteristics of decorator mode: adding some functions to the object while the program is running without changing the object itself, in a word: icing on the cake. (Think about the most profitable skin in “the glory of the king”, how is it all games, hello! )
IV. Scenarios-Slice Oriented Programming (AOP)
When it comes to decorators, the most classic application scenario is Aspect Oriented Programming (AOP). AOP is suitable for some applications with horizontal logic (sliceable), such as submitting forms. The logic executed after clicking the submit button is: Submit Click-> Verify Data-> Submit Data-> Submit Results. It can be seen that the head-to-tail log reporting function is not directly related to the core business logic, and almost all form submissions require the log reporting function. Therefore, the log reporting function can be abstracted separately and finally dynamically woven into the business logic when the program is running (or compiled). Similar functions include data verification, permission control, exception handling, cache management, etc.
The advantage of AOP is that it can keep the purity and high cohesion of business logic modules, and at the same time it is convenient to reuse functions. Through decorators, functional modules can be easily decorated into the main business logic.
V applications-applications in front-end development
Next, let’s look at how specific decorator patterns are applied in front-end development.
Talk is cheap, show me the code! (Cut the bullshit and put the code here! )
Changing an object in JS is as simple as that.
Because JS is a prototype-based weak type language, adding or modifying functions to objects is very easy. Therefore, the traditional object-oriented decorator pattern has not been widely used in JS (scenes have increased since ES6 formally proposed class).
Let’s first briefly simulate the decorator pattern in object-oriented.
Let’s say we want to develop a game of airplane warfare. Airplanes can switch their weapons and fire different bullets.
Let’s first implement an airplane class and implement a fire method.
Next, we implement a missile-launching decorator class
This class receives an airplane instance and re-implements the fire method, calling the fire method of the original instance inside the method, then expanding the method to increase the function of launching missiles.
Similarly, we have realized another decorative device for launching atomic bombs.
Finally, let’s look at how these two decorators should be used.
It can be seen that after two instances of plane decorated by decorators, when the fire method is called again, three sub-bombs can be fired at the same time. The decorator itself does not directly rewrite the plane class, but only enhances its fire method and is transparent to users of the Plane instance.
Next, let’s take a look at how to use decorators to implement AOP programming in JS.
First, let’s expand the prototype of the function so that each function can be decorated. We add a before and after method to the function. The two methods each receive a new function and ensure that the new function is executed before or after the original function.
It should be noted here that the new function and the original function have the same this and parameters.
With two methods, many complicated requirements have become very simple before.
Chestnut 1: Mount Multiple onload Functions
Under normal circumstances, window.onload can only mount one callback function and repeatedly declare the callback function. Later, it will overwrite the previous declaration. after, the trouble is solved.
Chestnut 2: Log Report
Chestnut 3: Add (Change) Parameters
For example, to increase security, add a token parameter to all interfaces. If AOP is not practical, we can only change ajax methods. But with AOP, you can do the following.
The principle is that the before function and the original function receive the same this and parameters, and before will execute before the original function.
In fact, AOP has many application scenarios in front-end projects, such as checking form parameters, exception handling, data caching, local persistence, etc. Here are not all examples.
Some students are more resistant to directly rewriting the prototype of the function. Here we also give the before implementation of the function.
VI. ES7-@decorator syntax
In JS’s future standard (ES7), decorators have also been added to the proposal.
The front-end students all know that the biggest characteristic of jQuery is the API design of its chain call. Its core is that every method returns this, which is the instance of jQuery object. We might as well implement a high-order function to implement chain call first.
The fluent function receives a function fn as a parameter, returns a new function, calls fn through apply inside the new function, and finally returns the context this. With this function, we can easily add chain calls to methods of any object.
Next, let’s look at how ES7 @decorator syntax can be used to simplify the above code. Let’s look at the results first.
Those who are familiar with JAVA can see at a glance that this is not a notation. Yes, the @decorator in ES7 was designed with reference to Python and JAVA syntax. FluentDecorate after @ is a decorator function. This function receives three parameters, namely target, name and descriptor. These three parameters are exactly the same as those of the Object.defineProperty method. In fact @decorator is just the syntax sugar of this method.
It is worth noting that @decorator can not only act on the object or method of the class, but also directly act on the class. The difference is that the first parameter target of the decoration function is different. When acting on the method, target points to the object itself, while when acting on the class, target points to the class, and both name and descriptor are undefined.
The complete implementation of fluentDecorate function is given below.
In general, we can abstract this decoration function again to make it a higher-order function. We can receive fluent function or other functions (such as interception function, etc.) that we initially defined, and then return a new decoration function decorated with this function, which is more general.
@decorator is only a proposal so far. No browser supports this syntax, but fortunately Babel can be used to experience it in his own project in the form of plug-ins (transform-decorators-legacy).
Note that @decorator can only be applied to classes and methods of classes, not ordinary functions, because functions have variable promotion, while classes do not.
VII. Application of Component-Decorator in React Project
Finally, combining with React, the most popular front-end frame, we will look at how the decorator is used on the components.
Returning to the initial assumption, how to develop a game with rich details such as “Wilderness Breath”, let’s use React with decorators to simulate the implementation of details in the game.
Let’s first implement a Person component, which is used to refer to the main character of the game. This component can receive initialization parameters such as name, life value, attack class, etc. and display these parameters in a card. When the life value is 0, it will prompt “Game End”. Moreover, a “JUMP” button is placed in the card, and the click button is used to simulate the jump interaction of the main character.
The result is as follows, isn’t it very abstract? Ha ha!
Next, we want to simulate the weather and temperature changes in the game. We need to implement a “Natural environment” component, which has two states of weather (wat) and temperature (tep), and can change these two states through input. The Person component we created earlier is inserted into this component as a descendant, and the wat and tep states of Natural are received as attributes.
Well, our experiment page is completed, and the final effect is as follows. The progress bar and radio buttons can be used to change the weather and temperature, and the changed results are transmitted to the main players of the game through props.
However, changing the temperature and weather will not affect the main character. Next, we want to realize two functions without changing the original Person component. First, when the temperature is greater than 50 degrees or less than 10 degrees, the life value of the main character will slowly decrease. Second, when the weather is rainy, the hero fails once every three jumps.
First, the first function will be realized. When the temperature is too high or too low, the life value of the protagonist will slowly decrease. Our idea is to realize a decorator, which is used to decorate the Person component externally so that the component can sense temperature changes. First, give the implementation:
Carefully observe the decorateTep function. It receives a component (A) as a parameter, returns a new React component (B), maintains an hp and tep state inside B, changes the hp of B when tep is at a critical value, and finally uses the hp of B to replace the original hp attribute to pass to component A when rendering.
Isn’t this a high-level component (HOC)? ! Yes, when a decorator decorates a component, its implementation is exactly the same as that of high-level components. The ability of the original component is enhanced by returning a new component, which is also in line with the design pattern of component combination advocated by React (note that it is not mixin or inheritance). The use method of decorateTep is very simple, with one line of code:
Next, let’s implement the second function. Jumping will occasionally fail when it rains. Here, let’s change the strategy and not decorate the Person component, but decorate the onJump jumping method inside the component. The code is as follows:
Distinguishing from the previous decorateTep, the decorateWat decorator focuses on the third parameter descriptor. as mentioned earlier, the descriptor parameter is the description object of the decorated method, and its value attribute points to the original method (onJump). here, we use the variable method to save the original method, while I record the number of clicks. The life cycle of these two variables is prolonged by closure. Finally, a new method is implemented to replace the original method. The original method is called by apply inside the new method and variable I is reset. Note that decorateWat finally returns the changed descriptor object.
The onJump method decorated by the decorator is as follows:
All right, next is the moment to witness the miracle!
VIII. Wheels-Common Decorator Library
In fact, there are already many libraries of open source decorators that can be used now. The following are the wheels with better quality. I hope I can help you.
IX. References-Reading Relevant Information
All demo source code
Five minutes will show you why Zelda can win the game of the year.
Forty-six Wonderful Little Details in “Wilderness”
Comments on “Wilderness Interest” by Last Player in Japan and Asia
“Introduction to ES 6 Standard (2nd Edition)” Ruan Yifeng; publishing house of electronics industry
Finally, if there is anything wrong, welcome friends to leave messages and clap bricks. Your support is my greatest motivation to continue!
Thank you all!