当前位置:网站首页>Typescript: use polymorphism instead of switch and other conditional statements

Typescript: use polymorphism instead of switch and other conditional statements

2022-06-26 06:58:00 Code Taoist

Use OOP Principles for writing better code .

In limine , I use as much as I can switch sentence . For me, , It looks perfect , Until one day I read a book called “ Tidiness code ” The book of .Robert C. Martin The book clearly explains why switch Statement can be a bad structure , And why we might reconsider and stop using them anywhere .

With the passage of time and the accumulation of experience , I use less and less switch sentence , later , I don't see any reason to use them . In this paper , I would like to share an alternative that I have used in my project switch Method of statement . It is called polymorphism , It's also OOP One of the principles of .

Why reconsider using switch sentence ?

switch Statements always cause methods to be verbose , Dozens of conditions , Each instruction requires at least a few lines of code plus break.Robert C. Martin In his 《 Tidiness code 》 It is said in a book that this method “ Use as little as possible ”.

Besides ,switch Statements are not readable and extensible .

Must filter through hundreds of criteria ( Multiple cases —— Single operation ), This is not user friendly , And may confuse others .

In a switch Statements can be mixed with completely different logic , Of course, this is wrong , Because of the method “ You should only do one thing and do it well ”.

What do we have ? One switch sentence ?

This is the initial code we will refactor in this article . We have a switch Statement and a code to enumerate various fruits .

export enum Fruits {
  Apple = 1,
  Lemon = 2,
  Orange = 3,
  Banana = 4,
}

function getPrice(fruit: Fruits): number {
  let result: number;

  switch (fruit) {
    case Fruits.Banana:
      result = 11.1;
      break;
    case Fruits.Lemon:
      result = 6.5;
      break;
    case Fruits.Orange:
      result = 7.7;
      break;
    case Fruits.Apple:
      result = 5.2;
      break;
    default:
      result = 0;
  }

  return result;
}

We can get rid of switch sentence , Because we just need to enumerate .

Let's get rid of switch Statement!

First , Let's add a new one called Fruit The abstract class of , Express the basic entity in terms of price .

export abstract class Fruit {
  protected constructor(readonly price: number) {}
}

Fruits Classes should be abstract , Because we don't want to create base class instances . The inherited class will provide the value of the price .

export const Fruits: Record<FruitType, new () => Fruit> = {
  Apple: class extends Fruit {
    constructor() {
      super(5.2);
    }
  },
  Banana: class extends Fruit {
    constructor() {
      super(11.1);
    }
  },
  Lemon: class extends Fruit {
    constructor() {
      super(6.5);
    }
  },
  Orange: class extends Fruit {
    constructor() {
      super(7.7);
    }
  },
};

Here we can see that we used Record Utility type .

Now? , Let's add a function that provides the correct constructor .

const findFruitConstructor = (fruitEnum: FruitEnum): new () => Fruit => Fruits[fruitEnum];

Start the first test :

const Banana = findFruitConstructor(FruitEnum.Banana);
console.log(new Banana());

// or just
console.log(new Fruits[FruitEnum.Apple]());

Overloaded constructor factory function

Sometimes , It is convenient to pass strings instead of enumerating values . for example , We may start from HTML Get a string from the template , We should return the price for it .

But before we update the constructor factory function , We need to add another function to help us use enumeration :

function enumToEntries(enumSrc: any): [string, number][] {
  const onlyNames = (value: string) => 
    typeof enumSrc[value] === "number";
  const asEntry = (value: string) =>
    [value, parseInt(enumSrc[value])] as [string, number];

  return Object.keys(enumSrc).filter(onlyNames).map(asEntry);
}

It simply enumerates and returns an array with tuples . Each tuple consists of an enumeration name and a value .

To use strings and enumeration values , We need to update our constructor factory function :

function findFruitConstructor(
  constructorName: string | FruitEnum
): new () => Fruit | never {
  const fruitEntries = enumToEntries(FruitEnum);
  const currentEntry = fruitEntries.find((entry: [string, number]) => {
    return entry.includes(constructorName);
  });

  const result =  currentEntry 
    ? () => Fruits[currentEntry[1] as FruitEnum] 
    : () => { 
      throw new Error('Given fruit doesn't exist.') 
    };

  return result();
}

Let's test by passing a string value findFruitConstructor function :

const Banana = findFruitConstructor('Banana');
console.log(new Banana());

// Will throw an error
const NotExisting = findFruitConstructor('NotExisting');
console.log(new NotExisting());

Use Nullable Patterns provide default behavior

If we somehow get the name of a constructor that doesn't exist , What will happen ?

We'll get a mistake .

To prevent this and set the default behavior for non-existent fruits , We can use Nullable Patterns instead of errors .

Let's add a Nullable Interface :

interface Nullable {
  isNull(): boolean;
}

Fruit Class should implement what must be returned Nullable Interface and return false Of .isNull() Method .

abstract class Fruit implements Nullable {
  protected constructor(readonly price: number) {}

  isNull(): boolean {
    return false;
  }
}

We also need to add NullableFruit, This class represents a fruit without behavior :

class NullableFruit extends Fruit {
  constructor() {
    super(0);
  }
  isNull(): boolean {
    return true;
  }
}

Now we need to return one NullableFruit Constructor instead of throwing an error .

function findFruitConstructor(
  constructorName: string | FruitEnum
): new () => Fruit {
  const fruitEntries = enumToEntries(FruitEnum);
  const currentEntry = fruitEntries.find((entry: [string, number]) => {
    return entry.includes(constructorName);
  });

  return currentEntry ? Fruits[currentEntry[1] as FruitEnum] : NullableFruit;
}

This is the final demo:

const Banana = findFruitConstructor('Banana');
const banana = new Banana();
console.log(banana); // { "price": 11.1 } 
console.log(banana.isNull()); // false

// Won't throw an error
const NotExisting = findFruitConstructor('NotExisting');
const notExistingFruit = new NotExisting();
console.log(notExistingFruit); // { "price": 0 } 
console.log(notExistingFruit.isNull()); // true

It seems to work as expected . Besides , We have added many useful features in this process , And you can continue to add more functions .

原网站

版权声明
本文为[Code Taoist]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202171318130340.html