Key words: requirement realization, design pattern, strategy pattern, programmer growth

Period:

Starting from business scenarios, this paper introduces the process of disassembling difficult points, coding requirements, optimizing codes and thinking about personal growth in the face of a complex requirement.

  • It will introduce a practice using the strategy model.
  • The requirements and code themselves are less than the growth path of the monster upgrade.
  • The code in this paper is pseudocode.

Scenario Description:

Demand description: The “top up center” in Taobao should be placed in ele. me, Taobao Speededition, UC Browser and other group apps. After getting the demand, we will sort out the core function points involved in the “top up center” in other terminals:

  • Address book reading

The JSbridge API is implemented slightly differently for different clients and operating systems.

  • pay

Payment JSbridge is invoked differently by different ends.

  • Account system:

Account systems at different ends of the group may be different, so you need to get through.

  • The container is compatible with

PHA container is used in hand cleaning. The minimalist version of Taobao is put into H5, and ele. me is put into small procedures of hand cleaning. Environment variables and communication modes need to be compatible.

  • Each end personalized appeal

Speedier version of the minimalist link, only retain the core module.

The solution

The requirement is clear: recharge related core modules need to be compatible with each APP. In essence, it is to provide a multi-end delivery solution. So how is this scenario coded?

1. Plan 1

The first idea is 💡, determine the client environment with if-else in each function point module and write this logic. To obtain the address book list function as an example, the code is as follows:

// business code file index.js
/** * Get address list *@param ClientName End name */
const getContactsList = (clientName) = > {
  if (clientName === 'eleme') {
   	getContactsListEleme()
  } else if (clientName === 'taobao') {
   	getContactsListTaobao()
  } else if (clientName === 'tianmao') {
    getContactsListTianmao()
  } else if (clientName === 'zhifubao') {
    getContactsListZhifubao()
  } else {
    / / the other side}}Copy the code

Once you’re done, review your code and think about the pros and cons of coding this way.

Pro: Clear logic and fast implementation. Disadvantages: the code is not beautiful, slightly poor readability, each compatible end must be changed in the business logic, change one end to test multiple ends.

At this time, some students said: “change the if-else into switch-case writing, the access to the address book module abstract into an independent SDK package, users in the business layer unified call”, genius! Do it.

2. Plan 2

The core function module is abstracted into an independent SDK. It is compatible with different ends inside the module and is called in a unified way in business logic.

/** * Get the address book list SDK caontact.js *@param ClientName End name *@param SuccessCallback successful callback *@param FailCallback Callback */
export default function (clientName, successCallback, failCallback) {
  switch (clientName) {
    case 'eleme':
      getContactsListEleme()
      break
    case 'taobao':
     	getContactsListTaobao()
      break
    case 'zhifubao':
      getContactsListTianmao()
      break
    case 'tianmao': 
      getContactsListZhifubao()
      break
    default:
      / / to omit
      break}}// business calls index.js
<Contacts onIconClick={handleContactsClick} />

import getContactsList from 'Contacts'
import { clientName } from 'env'
const handleContactsClick = () = > {
  getContactsList(
    clientName,
    ({ arr }) = > {
      this.setState({
        contactsList: arr
      })
    },
    () = > {
      alert('Failed to get address book')})}Copy the code

As a convention, review the code:

Advantages: module division is clear, business layer unified call, high code readability. Con: There is no solution of multiple ends, each iteration, need each end regression.

The above implementation, which seems to improve the readability of the code, is a good design, but is it the best design?

3. Plan three

Those of you who are familiar with design patterns, at this point you might say, use strategic patterns, and by the way, use strategic patterns for this scenario. Here is a brief explanation of Strategy Pattern: Strategy Pattern, the full English name is Strategy Design Pattern. In GoF’s Book Design Patterns, it is defined as follows:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Define a family of algorithm classes and encapsulate each algorithm so that they can be replaced with each other. Policy patterns can make changes to algorithms independent of the clients that use them (by client I mean the code that uses the algorithm).

It’s a little obscure. What does that mean? My personal interpretation is that policy patterns are used to decouple the definition, creation, and use of policies. It is typically used to avoid lengthy if-else or switch branch judgment encoding.

Here is the code implementation:

/** * Policy definition */
const strategies = {
  eleme: () = > {
   	getContactsListEleme()
  },
  taobao: () = > {
   	getContactsListTaobao()
  },
  tianmao: () = > {
    / / to omit}}/** * Policy creation */
const getContactsStrategy = (clientName) = > {
  if(! clientName) {throw new Error('clientName is empty.')}return strategies[clientName]
}
/** * policy uses */
import { clientName } from 'env'
getContactsStrategy(clientName)()
Copy the code

The application of strategy pattern, the definition, creation and use of policy decoupling, in line with Demeter’s law (LOD) in the design principle, to achieve “high cohesion, loose coupling”. When we need to add an adapter, all we need to do is change the policy definition Map, and no other code needs to be changed. This minimizes and centralizes code changes.

To do this, I believe you have surpassed some students, but we still need to think, keep improving, how to be better? At this time, thinking only from the coding level has been blocked, can you think from the perspective of engineering construction, performance optimization, project iteration process and later code maintenance, I believe you will have a better idea.

Here’s my own reflection:

4. Plan 4

From the perspective of engineering construction and performance optimization: If each end has an independent file, shake other chunks during construction, so that the bundle can be changed smaller and network requests can be changed faster.

And so on… Tree-shaking is based on ES static analysis. Our strategy is based on runtime.

Scheme 3 uses the policy mode to code, which is essentially policy definition, creation and use decoupling. Can we use the idea just now to aggregate the compatible methods of each functional module at each end into an independent module, so as to define, create and use decoupling of multi-terminal business policies from a higher dimension?

Think about what are the benefits of doing that?

Because the adaptation of each end is aggregated in one module, the multi-end business policies are uncoupled. When the policy of one end is changed, only this end Module needs to be modified, and the code changes are minor. In addition, there is no need to repeat regression to other ends for subsequent test links. Conforms to “high cohesion, loose coupling”.

Code implementation:

/** * policy definition module */
export const elmcStrategies = {
  contacts: () = > {
    getContactsListEleme()
  },
  pay: () = > {
    payEleme()
  },
  // Other functions are omitted
}
/** * Module */
export const tbStrategies = {
  contacts: () = > {
    getContactsListTaobao()
  },
  pay: () = > {
    payTaobao()
  },
  // Other functions are omitted
};
/ /... (Other ends omitted)
/** * create policy index.js */
import tbStrategies from './tbStrategies'
import elmcStrategies from './elmcStrategies'
export const getClientStrategy = (clientName) = > {
  const strategies = {
    elmc: elmcStrategies,
    tb: tbStrategies
    // ...
  }
  if(! clientName) {throw new Error('clientName is empty.')}return strategies[clientName]
};
/** * Policy uses pay */
import { clientName } from 'env'
getClientStrategy(clientName).pay()
Copy the code

The code directory is shown in the figure below: index.js is the entry to the multi-end policy, and other files are the implementation of each end policy.

According to the derivation of scheme 4, sometimes the judgment may not be right, but thinking from multiple dimensions will open up your mind, and then a better scheme will often come to you

5. Plan 5

We should not only solve the immediate pain points, but also make a long-term plan. Based on the above four solutions, we should think further. If the business needs to be put into a third party (non-group APP), such as the merchant APP, and the merchant APP has complex and changeable access to address book and payment logic, how to design the coding at this time? For example: pull up other end call end strategy, affected by many factors, involving product barriers, strategy attack and defense, how to control the number of code changes, timely improve call end rate? In this simple brick, can use the serverless in recent years is very popular, set up the faAS function of call end strategy, dynamically obtain the optimal call end strategy, is not a good plan?

Settling & Thinking

To solve the problem of multi-terminal compatibility, we learned and applied the design mode — strategy mode. So let’s look at the design idea of the policy pattern: When it comes to the policy pattern, some people think that its function is to avoid if-else branch judgment logic. In fact, this understanding is very one-sided. The main function of the policy pattern is to decouple the definition, creation and use of the policy, and control the complexity of the code, so that each part is not too complex and too much code. In addition, for complex code, the policy pattern also allows it to meet the open-closed principle, minimizing and centralizing code changes when adding new policies and reducing the risk of introducing bugs. In fact, design principles and ideas are more universal and important than design patterns. By understanding the design principles and ideas of code, we can better understand why we use a design pattern, and we can better apply the design pattern. It is also important to understand the business value and complexity of code design and avoid over-designing. Why bother with design patterns when you can solve an if-else problem?

conclusion

Straighten out the core path of the full text, which is also the main way I want to convey this article to upgrade the growth path.

Receiving a complex requirement — sorting out requirements — dismantling technical difficulties — coding implementation — optimizing code — learning design patterns and principles — drawing analogies — recording precipitation.

At present, front-end engineers will inevitably fall into the business vortex and be pushed by the business in their work. In the face of such risks, we need to think about how to abstract out common solutions and settle them by applying appropriate technical architecture on the basis of ensuring the completion of business iterations. In this way, we can not only help the business grow faster and more steadily, but also gain personal growth in the process.