学习TypeScript中的Pokemon

92 阅读4分钟

口袋妖怪实例/ TypeScript

比方说,你希望能够创造任何类型的小精灵

image.png

我知道这是一个大胆的要求,特别是由于一直有新的小精灵被添加和创造。

虽然这是事实,但如果我们想做到这一点,我们需要从某个地方开始。

因此,让我们定义一下Pokemon 抽象。

口袋妖怪抽象类

所有小精灵必须扩展这个抽象类

interface IPokemonProps {
  name: string;
  color: string;
}

abstract class Pokemon implements IPokemonProps {
  public name: string;
  public color: string;

  constructor (props: IPokemonProps) {
    this.name = props.name;
    this.color = props.color;
  }

  abstract attack (): IAttack;
}

每个小精灵都必须有一个name 和一个color具体的小精灵类必须实现抽象的attack 方法。不同的小精灵有不同的攻击,对吗?这就是为什么它是抽象的。子类将定义它。

好了,让我们来创建我们的第一个小精灵。所以让我们从皮卡丘开始。

我们如何创建一个皮卡丘?

我们的第一个工厂,一个皮卡丘工厂

我不确定在节目中是否真的讨论过宠物小精灵是如何创建的......

假设一下,如果我们想创造一只皮卡丘,我们需要做以下事情。

  • 得到一堆电池
  • 得到一些胶带
  • 得到一些油漆
  • 找一只猫
  • 把电池贴在猫的背上

在这里发挥你的想象力,好吗?

由于创造这种特殊的小精灵显然有一个过程,让我们把它放到一个抽象的工厂中。

class Pikachu extends Pokemon {
  private cat: TapedItem<Battery[], Cat>;

  constructor (cat: TapedItem<Battery[], Cat>) {
    super({ name: 'Pikachu', color: 'yellow' });
    this.cat = cat;
  }

  attack () : ZapAttack {
    return this.cat.zapAttack();
  }
}

class PikachuFactory {
  public static create (): Pikachu {
    const batteries: Battery[] = [
      new Battery(),
      new Battery()
    ];
    const paint: Paint = new Paint('yellow');
    const tape: Tape = new Tape();
    const cat: Cat = new Cat();

    const paintedCat: PaintedItem<Cat> = Paint.paintItem(cat, paint);

    const catTapedByBatteries: TapedItem<Battery[], Cat> = Tape
      .combineItems(batteries, paintedCat);

    return new Pikachu(catTapedByBatteries);
  }
}

好的,太棒了,我们有一个方法来创造皮卡丘斯。

我们可以像这样做。

const pikachu: Pikachu = PikachuFactory.create();

我们已经把所有复杂的皮卡丘创建逻辑封装在一个工厂里了。

呜呼!

更多口袋妖怪工厂

现在让我们假设,我们想创建一个Charmander工厂,一个Bulbasaur工厂和一个Porygon工厂。它们中的每一个都有同样的创造性和复杂的方法来创造它们,被封装在某种类型的口袋妖怪工厂中。

而我们希望能够像这样创造它们。

const charmander: Charmander = CharmanderFactory.create();
const bulbasaur: Bulbasaur = BulbasaurFactory.create();
const porygon: Porygon = PorygonFactory.create();

还有更多!

尽管创造更多的宠物小精灵工厂会很有趣,但我必须假设你明白这个想法。


在这一点上,我终于可以向你介绍抽象工厂的作用了。

理想情况下,我们希望以某种方式来封装小精灵的创建。

根据定义。

抽象工厂模式提供了一种方法来封装一组具有共同主题的单个工厂,而不需要指定它们的具体类。

考虑到上下文,这个定义现在可能更有意义。

我们不需要像这样通过导入我们想要的小精灵类型来创建一个硬的源代码依赖关系

import { Charmander } from 'pokemon/charmander'
import { CharmanderFactory } from 'pokemon/charmander/factory'
import { Bulbasaur } from 'pokemon/bulbasaur'
import { BulbasaurFactory } from 'pokemon/bulbasaur/factory'
import { Porygon } from 'pokemon/porygon'
import { PorygonFactory } from 'pokemon/porygon/factory'

const charmander: Charmander = CharmanderFactory.create();
const bulbasaur: Bulbasaur   = BulbasaurFactory.create();
const porygon: Porygon       = PorygonFactory.create();

我们可以像这样调用一个abstract PokemonFactory

import { PokemonFactory, PokemonType } from 'pokemon/factory'
import { Charmander } from 'pokemon/charmander'
import { Bulbasaur } from 'pokemon/bulbasaur'
import { Porygon } from 'pokemon/porygon'

const charmander: Charmander = PokemonFactory.create(PokemonType.CHARMANDER);
const bulbasaur: Bulbasaur   = PokemonFactory.create(PokemonType.BULBASAUR);
const porygon: Porygon       = PokemonFactory.create(PokemonType.PORYGON);

PokemonFactory的样子。

import { CharmanderFactory } from 'pokemon/charmander/factory'
import { BulbasaurFactory } from 'pokemon/bulbasaur/factory'
import { PorygonFactory } from 'pokemon/porygon/factory'

enum PokemonType {
  CHARMANDER = 'charmander',
  BULBASAUR = 'bulbasaur',
  PORYGON = 'porygon'
}

export class PokemonFactory {
  public static create(pokemonType: PokemonType): Pokemon {
    switch (pokemonType) {
      case PokemonType.CHARMANDER:
        return CharmanderFactory.create();
      case PokemonType.BULBASAUR:
        return BulbasaurFactory.create();
      case PokemonType.PORYGON:
        return PorygonFactory.create();
      default:
        return null;
    }
  }
}

为什么它是有用的?

我们在这里所做的是抽象化了我们如何创建小精灵

我们也将创建小精灵的单一责任委托给了一个地方。

当我们需要添加新的小精灵时,我们添加一个新的PokemonType ,创建新的工厂并将其添加到这个开关语句的结尾。