The hero of the story is called small M, he has been trying to make some money outside of work, one day, he had a whim, want to open an online shop, sell what? Recently released 13 incense iPhone, iPad, must be a lot of people buy, then I sell the case? Sure big sell, say dry dry, the next day he put his shop up leng.

Although the online store application is good, but his product has not been designed, so he began to work as an “architect”, design his products.

He wanted to allow his protective shell to customize color and material first. In order to facilitate the use of all protective shells, he made a parent class to achieve the two functions of setting color and setting material.

type Color = 'black' | 'red' | 'green';
type Material = 'rubber' | 'leather' | 'Rigid plastics'

classProtect shell{
  private color: Color = 'black'
  private material: Material = 'rubber'

  constructor() {
    // ...} set the color (color: color) {this.color = color} Set material (material: material) {this.material = material;
  }

  whoIAm() {
    console.log('I'm a protective shell.')}}Copy the code

After setting up the protective shell parent class, inherit the parent class of the iPhone protective shell and iPad protective shell code is very little, small M complacently said: look at my design level is high, later my shop is bigger, want to do Xiaomi mobile phone, Huawei mobile phone, do not need to write code, just need to inherit it!

class IPhone extendsProtect shell{
  whoIAm() {
    console.log('I'm an iPhone case')}}class IPad extendsProtect shell{
  whoIAm() {
    console.log('I'm an iPad case.')}}Copy the code

When a user said, “I want a red, leather iPad case,” M simply ran the following two lines of code using the iPad class, and whoosh!

const a = newIPad() a. Set material ('leather'A. Set the color ('red')
Copy the code

With the passage of time, his shop slowly become bigger, and support the millet mobile phone, millet tablet, Huawei mobile phone, Huawei tablet and other dozens of products.

Done this step, small M is not satisfied, I that is just a start, in order to occupy the top of the so-and-so platform, I need my protective shell out of the ordinary!

Let the shell support users DIY first?

How to achieve it, small M thought, it is simple, just add a DIY method in the parent class, so that all the protective shell we sell in the store can support DIY:

classProtect shell{...DIY() {
     console.log('I'm a DIY parent')
    // 用户自己 DIY}... }Copy the code

Indeed, all subclasses of protective shells can now do DIY. But it was only a brief dawn.

At this moment, little M encountered the first blow of opening an online store. Although he had designed a plan, the factory could not support DIY for iPad temporarily due to the lack of raw materials. However, our DIY method was added to all the protective shells, so we had to quickly get off the line.

What to do? Small M thought, this can not be difficult to me, I directly let the iPad shell class, write an empty DIY method, overwrite the parent class DIY method.

class IPad extendsProtect shell{
  DIY() {
      // Do nothing, just override
  }

  whoIAm() {
    console.log('I'm an iPad case.')}}Copy the code

This time the problem was solved, but there was another problem.

One customer asked huawei to do a bit more DIY with its tablet case. No way, the customer is dad ah, at this time small M had to find this protective shell, according to his requirements to modify the DIY function.

Later, a friend of the customer found that your protective case is good, but he uses iPad, so he also wants to have the same DIY function in iPad, and he has to go to the iPad subclass to modify.

As was often the case, he worked his way through each subclass and found more and more duplicate code that was hard to reuse.

One day, he decided to change the architecture of the code.

He found that DIY was often modified for various reasons, so as a first step, he decided to abstract it out:

type DeviceType = 'mi-11' | 'mid-pad-5' | 'iphone' | 'ipad'

interface DIYBehavior {
  DIY: () = > void
}
Copy the code

He then made two classes that implemented the interface, depending on whether it could be DIY.

If you can do DIY now, this is the class:

class CanDIY implements DIYBehavior {
  private deviceType: DeviceType

  constructor(deviceType: DeviceType) {
    this.deviceType = deviceType;
  }

  DIY() {
    console.log('I'm going to take thisThe ${this.deviceType}For DIY `)}}Copy the code

When DIY is not possible, use this:

class CanNotDIY implements DIYBehavior {
  private deviceType: DeviceType

  constructor(deviceType: DeviceType) {
    this.deviceType = deviceType;
  }

  DIY() {
    console.log('At present on thisThe ${this.deviceType}Can't do DIY ')}}Copy the code

We’ve completely taken the logic out of DIY, so how does that work?

If, for now, our iPad tablet supports DIY, we could organize our iPad class as follows:

class IPad extendsProtect shell{
  private ipadDIY: DIYBehavior

  constructor() {
    super(a);this.ipadDIY = new CanDIY('ipad')}DIY() {
    this.ipadDIY.DIY()
  }
  ...
}
Copy the code

In other words, we take DIYBehavior as an attribute of our protective shell, and we will do DIY through this attribute.

With that in mind, let’s imagine that if our tablet’s DIY capabilities were enhanced and all sorts of cool features were supported, we would have to change every DIY method that supported the DIY subclass from the previous design.

But for now, we don’t need to change them at all, we just need to change the DIY method of the CanDIY class!

Not only that, but if one of our classes suddenly doesn’t support DIY, it doesn’t matter, we can add a setter to change this variable at run time:

class IPad extendsProtect shell{
  private _ipadDIY: DIYBehavior

  constructor() {
    super(a);this._ipadDIY = new CanDIY('ipad')}set ipadDiY(ipadDiY: DIYBehavior) {
    this._ipadDIY = ipadDiY;
  }

  DIY() {
    this._ipadDIY.DIY()
  }

  whoIAm() {
    console.log('I'm an iPad case.')}}const a = new Ipad()

a.ipadDiY = new CanNotDIY('ipad')
a.DIY() // There is currently no DIY for this iPad
Copy the code

Have feeling this way organization is convenient, flexible a lot. We no longer have to worry about what the act of DIY does to our subclasses.

The design pattern we used above is the strategic pattern. The following is the official definition, but it doesn’t feel like “human language”.

As a software design pattern, policy pattern refers to a certain behavior of the object, but in different scenarios, the behavior has different implementation algorithms. For example, everyone has to “pay personal income tax”, but “pay personal income tax in the United States” and “pay personal income tax in the Republic of China” have different methods of calculating tax. — Wikipedia

I don’t know if the above example makes this definition a little bit more clear.

In the example above, we implement the DIYBehaviour interface using a class, which is natural in other languages, but is a bit out of line with JavaScript usage. After we understand the policy pattern, we follow its rules. We can also implement it using functions. This may be more comfortable and convenient to use:

function canDiY(deviceType: DeviceType) :DIYBehavior {
  return {
    DIY() {
      console.log('I'm going to take this${deviceType}For DIY `)}}}class IPad extendsProtect shell{
  private _ipadDIY: DIYBehavior

  constructor() {
    super(a);this._ipadDIY = canDIY('ipad') // Change the call form slightly}... }Copy the code

Here, little M finally happy to complete the reconstruction of this code, our story is over.

Thank you for reading, and wish you a happy Mid-Autumn Festival holiday.