preface

Why write this article? It is mainly through the code refactoring of a business component to communicate the issues that the front-end packaging component needs to pay attention to. This will also be interspersed with some design patterns of small knowledge points, easy to review the old and learn the new, if there is inaccurate understanding and design unreasonable place, welcome to correct.

Some time ago, I read a paragraph in an article called “Design Mode Discussion”. I can no longer agree with it. Here I post it:

The way to judge good code is not by what patterns it conforms to, but by whether it conforms to the following five design principles. Design mode is only a superficial routine, design principle is the central idea. Or design pattern is only a means, design principle is the end. As Zhang Sanfeng said to Zhang Wuji:

“Mowgli, do you remember?”

“All forgotten”

“Then you can play.”

Design patterns change and evolve as the language progresses, but design principles do not.

— Design Patterns in General

Will learn

  • Six Design principles
  • Use of policy patterns
  • How to encapsulate a set of sharing components for multiple environments

Look at the

First of all, why encapsulate sharing?

What are our needs?

Is a line of code, in a number of scenarios, can be shared to QQ friends, wechat friends, QQ space, wechat circle of friends function.

Multiple scenes here are simply webViews embedded in different apps. As for the H5 page of our project, the scenes that need to support sharing are as follows:

  • QQ
  • WeChat
  • Wechat applets
  • King of Life APP
  • King camp app
  • Handheld League of Legends APP
  • In-game MSDK browser (v3 and V5)
  • The in-game Slug browser
  • System Built-in browser
  • And so on.

We need to realize this sharing component by ourselves. Obviously, it is unreasonable for the business side to decide which to share in so many scenarios. What we need is the sharing function with a line of code.

Pre-refactoring code

This is exactly what we did at the beginning of the package. When sharing components are used externally, you don’t need to care about the specific scenario, just call initShare(options) to initialize the sharing configuration or openShareUI() to pop up the share window.

Obviously, this is consistent with the “interface isolation principle” : the interfaces exposed to the user are small and complete.

But there were some problems with the internal implementation of our original sharing component:

The original share component is a share.js. Let’s take a look at the code implementation:

const shareUiObj = {};
// Handle the sharing UI for MSDK and SlugSDK
shareUiObj.initCommShareUI = function (callback) {
  // ...
};
// Handle the sharing UI for wechat and QQ
shareUiObj.initCommShareTip = function () {
  // ...
};
/** * Open the custom share UI component *@exports openShareUI* /
const openShareUI = function () {
  // ...
};
/ * * * initialization method obj: {title: 'title, desc:' description 'icon:' icon ', link: 'address' callback: '} *@exports initShare
 * @param Obj share parameter */
const initShare = function (obj) {
  if (typeof obj === 'undefined') obj = {};
  obj.title = obj.title || document.getElementsByTagName('title') [0].innerText;
  obj.desc = obj.desc || obj.title;
  obj.link = obj.link || window.location.href;
  obj.icon = obj.icon || 'http://ossweb-img.qq.com/images/pmd/igameapp/logo/log_igame_3.0.png';
  obj.type = obj.type || null;
  shareObject = obj;
  isWzydShare = obj.isWzydShare;

  const env = getEnv();
  if (env.isMsdk) {
    initMsdkShare();
  } else if (env.isGHelper) {
    initGHelperShare();
  } else if (env.isQQ) {
    initQQShare();
  } else if (env.isMiniProgram) {
    initMiniProgramShare();
  } else if (env.isWeixin) {
    initWeixinShare();
  } else if (env.isPvpApp) {
    initPvpShare();
  } else if (env.isTipApp) {
    initTipShare();
  } else if(env.isSlugSdk) { initInGameShare(); }};/ / MSDK share
function initMsdkShare() {
  // ...
}
/ / slugsdk share
function initInGameShare() {
  // ...
}
// King camp share
function initGHelperShare() {
  // ...
}
/ / QQ to share
function initQQShare() {
  // ...
}
// Wechat share
function initWeixinShare() {
  // ...
}
// Applet sharing
function initMiniProgramShare() {
   // ...
}
/ /... Implementation of other environments
Copy the code

Existing problems

Some problems can be found:

  1. If we need to add a new environment, our exposed initShare will have to be modified. This is clearly not in line with the “open and closed principle” : open to expansion, closed to revision.
  2. In the same file, there is the implementation of sharing in various environments, as well as the PROCESSING of UI. The whole share.js has 500+ lines of code, which does not conform to the “single responsibility principle” : a class has only one responsibility.
  3. There is no type verification, internal implementation or external call interface, if there is a parameter error, it is difficult to find.

Obviously, this code has a lot of room for optimization.

Now that we’ve shown off the three principles of design patterns, let’s take a look at all six and see what we can improve on:

  1. Dependency inversion principle: high-level modules should not depend on low-level modules.
  2. Open and closed principle: open to expansion, closed to modification.
  3. Single responsibility principle: A class has only one responsibility.
  4. Substitution principle (Richter’s substitution principle) : Subclasses must be able to replace their parents.
  5. Interface isolation principle: The interfaces exposed to users are small and complete.
  6. Demeter’s Law (least Knowledge principle) : one class should know the least about another.

“Substitution principle” is the premise of programming to an interface, the realization of the original didn’t do it, but we demand analysis: (share) to the same function for multiple implementations (supports multiple environment), programming to an interface is obviously a very good choice, while “dependency inversion principle” and “the law of Demeter” behind us to carry on the programming to an interface, also need to be aware of.

To operate

Let’s get started!

Make plan

For better type support, we use TypeScript to implement components. Although our front-end engineering is still vue2, there is no problem mixing TS components with JS code. We just need to add the TS configuration file tsconfig.json and install the following two dependencies.

npm install --save-dev typescript
npm install --save-dev @vue/cli-plugin-typescript
Copy the code

Combined with the interface oriented programming we identified above, there is a “policy pattern” in the design pattern that fits our need for multi-environment support.

To determine the interface

First we need to decide what kind of interface to provide externally. Just like our original implementation, we need to:

  • InitShare interface to initialize share configuration
  • OpenShareUI interface, the share window is displayed

So we have this interface design:

export interface ShareOptions {
  title: string;  // Share the title
  desc: string;   // Share the description
  link: string;   // Share the link
  icon: string;   // Share thumbnailsminiprogramLink? :string;  // Small program jump link, optional
  customShare: any;          // Customize share configuration, optional
  callback: () = > void       // Share successful callback
}

export interface ShareInterface {
  initShare(options: ShareOptions): void;
  openShareUI(): void;
}
Copy the code

Implementing an interface

After the interface is determined, the realization of our various environments is the realization of the interface, such as the following QQ share:

import { ShareInterface, ShareOptions } from '.. /share-interface';
import loader from '.. /.. /little-loader';
import ShareUI from '.. /share-ui';

/ / share QQ interface reference documents: / / http://mqq.oa.com/api.html#js-mqq-core
export default class ShareQQ implements ShareInterface {
  public initShare(options: ShareOptions) {
    console.log('share QQ', options);
    ShareUI.initCommShareTip();
    loader('https://open.mobile.qq.com/sdk/qqapi.js?_bid=152'.() = > {
      const { mqq } = window as any; mqq? .ui? .setOnShareHandler((type: any) = > {
        if (type= =0 || type= =1 || type= =2 || type= =3) {
          const param = {
            title: options.title,
            desc: options.desc,
            share_type: type.share_url: options.link,
            image_url: options.icon,
            back: true.uinType: 0};const callback = function () { options.callback? . (); }; mqq.ui.shareMessage(param, callback); }}); }); }public openShareUI(): void{ ShareUI.showCommShareTip(); }}Copy the code

In other environments, the implementation is similar, and there is no coupling between the various implementations, which conforms to Demeter’s law.

Extract common functions

In order to comply with the single responsibility principle, you can see that in the code above, the PROCESSING of the UI is isolated as a ShareUI class that is responsible for sharing UI-related functions.

External exposed interface

Finally, what we need to do is expose the functionality we implement. Since sharing is a global function, it is best to provide a singleton for sharing processing that the user can invoke directly.

class Share {
  private static instance: Share = new Share();

  public static getInstance() {
    return this.instance;
  }

  public static initShare(options: ShareOptions|any) {
    // options populates the default data
    if (typeof options === 'undefined') options = {};
    options.title = options.title || document.getElementsByTagName('title') [0].innerText;
    options.desc = options.desc || options.title;
    options.link = options.link || window.location.href;
    options.icon = options.icon || 'http://ossweb-img.qq.com/images/pmd/igameapp/logo/log_igame_3.0.png';

    this.getInstance().sharehandle.initShare(options);
  }

  public static openShareUI() {
    this.getInstance().sharehandle.openShareUI();
  }

  private sharehandle: ShareInterface;

  constructor() {
    const env = initEnv();
    if (env.isMsdkV5) {
      this.sharehandle = new ShareMSDKV5();
    } else if (env.isMsdk) {
      this.sharehandle = new ShareMSDKV3();
    } else if (env.isGHelper) {
      this.sharehandle = new ShareGHelp();
    } else if (env.isQQ) {
      this.sharehandle = new ShareQQ();
    } else if (env.isMiniProgram) {
      this.sharehandle = new ShareMiniprogram();
    } else if (env.isWeixin) {
      this.sharehandle = new ShareWx();
    } else if (env.isPvpApp) {
      this.sharehandle = new SharePvpApp();
    } else if (env.isTipApp) {
      this.sharehandle = new ShareTipApp();
    } else if (env.isSlugSdk) {
      this.sharehandle = new ShareSlugSdk();
    } else if (env.isLolApp) {
      this.sharehandle = new ShareLolApp();
    } else {
      this.sharehandle = newShareOther(); }}}export default Share;
Copy the code

As you can see, the initShare and openShareUI methods we provide do not need to care about the specific environment, just call the ShareHandle method, the specific environment is handled in the build method.

At the same time, we can find that we can completely replace the shareHandle interface with ShareQQ, which is the “substitution principle”.

The directory structure of the component

Finally, let’s take a look at the structure of the shared component now, which is not necessarily less code, but certainly much cleaner than the original 500+ lines of code.

How do YOU extend components?

If we need a new environment now, what do we need to do?

After refactoring, we just need to add an implementation of the interface in the plAT directory, add an environment judgment in the constructor, and that’s it. The method of real exposure to the outside world, we do not need to modify, and we do not need to modify, this is the “open closed principle”.

Pay attention!

Here’s another post about my understanding of design patterns, which was also mentioned in the first issue, but is really important.

  • The benefits of using TypeScrpit, besides type checking, are “better interface programming”!!
  • It doesn’t matter what design pattern you use!! Design patterns are just a means to make design conform to design principles, and even if you don’t learn design patterns, conforming to design principles is good code.
  • Design pattern still need to learn!! Learning design patterns makes it easier to design good code that conforms to design principles.

reference

Get started with TypeScript quickly

General discussion of design patterns

The strategy pattern

Past oliver

The first issue of goodbye to Bad Code

【 Front-end Exploration 】 Say goodbye to bad code! Encapsulate network requests in the chain of responsibility pattern

The other piece

[Thinking on three years of front-end development] How to read requirements effectively?

[Front-end exploration] Best practices for image loading optimization

[Front-end exploration] Exploration of mobile terminal H5 to generate screenshot posters

[Front-end Exploration] H5 to obtain user positioning? This one is enough

【 Front-end exploration 】 The exploration of wechat small program jump — Why does open label exist?

VConsole fancy usage