木灵鱼儿

木灵鱼儿

阅读:539

最后更新:2022/05/29/ 21:31:46

创建型模式:工厂模式(包含抽象工厂)

简介

工厂模式是讲需要实例化的代码提取出来,放到一个专门的类中去做管理和维护,达到和主体代码的解耦,从而提高项目的扩展性和维护性。将创建实例与调用实例进行解耦!

简单来说就是把new操作(创建实例)放到一个专门的地方统一管理,而不是在代码中耦合使用,方便管理和扩展。

工厂模式有三种用法:简单工厂模式、工厂方法模式、抽象工厂模式

简单工厂模式

简单工厂模式是指由一个工厂对象决定创建出哪一种产品类的实例。

比如我们需要建一个饭店,这个饭店需要招三名厨师,他们要有各自的拿手菜:水煮牛肉、青椒炒蛋、炒米粉

为了能方便的招聘厨师,我定了一个要求,厨师必须会:开火、颠勺、关火、出菜

于是我写了一个接口来要求这些厨师。

abstract class Cooks {
  abstract openFire(): void; //开火
  abstract dianshao(): void; //颠勺
  abstract shutFire(): void; //关火
  abstract accomplish(): string; //出菜
}

通过这个接口我找到三名厨师:小王、小明、小红

class xw extends Cooks {
  openFire() {
    console.log("开火,开始准备水煮牛肉");
  }
  dianshao() {
    console.log("颠勺");
  }
  shutFire() {
    console.log("关火,水煮牛肉快好了");
  }
  accomplish() {
    return "水煮牛肉";
  }
}

class xm extends Cooks {
  openFire() {
    console.log("开火,开始准备青椒炒蛋");
  }
  dianshao() {
    console.log("颠勺");
  }
  shutFire() {
    console.log("关火,青椒炒蛋快好了");
  }
  accomplish() {
    return "青椒炒蛋";
  }
}

class xh extends Cooks {
  openFire() {
    console.log("开火,开始准备炒米粉");
  }
  dianshao() {
    console.log("颠勺");
  }
  shutFire() {
    console.log("关火,炒米粉快好了");
  }
  accomplish() {
    return "炒米粉";
  }
}

现在我厨师有了,就需要考虑,怎么让厨师去做菜,饭店都是有人专门管理这个事情的,它负责跟厨师和外部沟通,我们称之为厨房管理员

这个管理员就可以写成工厂模式,它接受一个菜名,便叫厨师做菜,厨师做完了,就可以出菜了。

class KitchenManager {
  private cook: Cooks | undefined;

  cookFood(foodName: string) {
    if (foodName === "水煮牛肉") {
      this.cook = new xw();
    } else if (foodName === "青椒炒蛋") {
      this.cook = new xm();
    } else if (foodName === "炒米饭") {
      this.cook = new xh();
    } else {
      throw new Error("这个菜做不了");
    }

    //具体做菜逻辑
    this.cook.openFire();
    this.cook.dianshao();
    this.cook.shutFire();

    return this.cook.accomplish();
  }
}

此时一个简单工厂模式就搭建完成,我们甚至可以把具体的做菜逻辑封装到每个厨师的类上面,我们需要负责调用accomplish拿到做好的菜品即可。

当我们需要增加新的菜品时,外部的使用代码并不用改动逻辑,只是传入的参数不同而已,而我们只需要改动厨房管理员这部分代码,比如增加更多的else if判断,或者招聘更多的厨师,以满足客户需求。

简单工厂也称之为静态工厂,因为上述的管理员代码,完全可以转成静态方法使用,功能不受影响。

但是简单工厂也是有缺点的,虽然整体代码满足开闭原则,但是工厂本身是违背开闭原则的,因为每增加一个新厨师,或者新菜品,工厂一定会增加一个判断,比如else if

工厂方法模式

工厂方法模式指定义一个创建对象的接口,但是让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。

它用于解决简单工厂中特别多的if判断的问题,当我们的店铺越来越大的时候,菜品和做菜师傅也越来越多,那么KitchenManager承担了特别多的逻辑,成了一个全能工厂,这显然不太好维护。

我们将每个菜品分类为不同的菜系,通过不同菜系工厂去调用对应的厨师,再产出实际的食物。然后管理员只提供不同菜系的接口,由顾客去选择。

interface Factory {
  create(name: string): void;
}

接口抽象定义好后,我们的工厂就要对其进行自己的具体实现,由于懒得写厨师类了,厨师的代码就不要当真,因为如果要按照真实逻辑,一个厨师可能会多个菜品,他们自己会有对菜品判断是否可以制作的方法,这些都是细节,知道逻辑就行了。

//川系菜品
class CFactory implements Factory {
  private cook: Cooks | undefined;

  create(name: string) {
    if (name === "水煮牛肉") {
      this.cook = new xw();
    } else if (name === "水煮鱼") {
      this.cook = new xw();
    } else {
      throw new Error("这个菜做不了");
    }

    //具体做菜逻辑
    this.cook.openFire();
    this.cook.dianshao();
    this.cook.shutFire();

    return this.cook.accomplish();
  }
}

//湘系菜品
class XFactory implements Factory {
  private cook: Cooks | undefined;

  create(name: string) {
    if (name === "青椒炒蛋") {
      this.cook = new xm();
    } else if (name === "霸王别姬") {
      this.cook = new xm();
    } else {
      throw new Error("这个菜做不了");
    }

    //具体做菜逻辑
    this.cook.openFire();
    this.cook.dianshao();
    this.cook.shutFire();

    return this.cook.accomplish();
  }
}

// 粤系菜品
class YFactory implements Factory {
  private cook: Cooks | undefined;

  create(name: string) {
    if (name === "炒米粉") {
      this.cook = new xh();
    } else if (name === "炒河粉") {
      this.cook = new xh();
    } else {
      throw new Error("这个菜做不了");
    }

    //具体做菜逻辑
    this.cook.openFire();
    this.cook.dianshao();
    this.cook.shutFire();

    return this.cook.accomplish();
  }
}

我实现了三个菜系的工厂,他们都会根据Factory抽象实现自己的具体实现。

工厂创建好后,怎么去调用工厂由顾客去决定,而管理员提供的是不同工厂的调用接口。

class KitchenManager {
  //湘菜
  public xc(foodName: string) {
    return new XFactory().create(foodName);
  }

  //川菜
  public cc(foodName: string) {
    return new CFactory().create(foodName);
  }

  //粤菜
  public yc(foodName: string) {
    return new YFactory().create(foodName);
  }
}

这样就写省去了if-else的代码,虽然实现了开闭原则,但是会产生大量的工厂。

再举个例子:

比如食堂打饭,学校一开始只有一个窗口,这个窗口里有:水煮牛肉,青椒炒蛋,炒米粉;

学生去打饭的时候只能排队从这一个窗口里购买食物,这个窗口里面的人忙的不可开交。一会拿炒鸡蛋,一会拿炒米粉等等...(简单工厂)

于是为了减少里面打菜人的负担,学校又新增了两个窗口,现在是三个窗口,每个窗口对应一个菜,学生自己选择自己要吃什么,在哪个窗口排队,从而减少了窗口内人的负担。(工厂方法)

抽象工厂模式

抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。

这种模式有点类似于多个供应商负责提供一系列类型的产品。

比如都是青椒炒蛋,有的厨师用辣的青椒做,有的厨师用不辣的青椒做。他们都属于青椒炒蛋,但是是不同的口味,这个口味可以看成是产品等级。

A厨师可以做:青椒炒蛋、霸王别姬,但是是辣的;

B厨师也可以做:青椒炒蛋、霸王别姬,但是是不辣的;

他们都是同一菜系:湘菜;但是有不同的口味。

顾客来吃饭时,根据自己的口味调用不同的工厂实现,比如能吃辣的和不能吃辣的,都可以点青椒炒蛋。只要说明能不能吃辣,管理员就能调用工厂提供的方法实现。

这时我们可以发现,工厂方法面对的是多个产品同一个产品等级(产品结构);而抽象工厂是面对多个产品多个产品等级。

多个产品等级,那么表示这个产品会被多个实例去实现,为了规范,就需要为这个产品定义一个接口,工厂依赖这个接口,实例也依赖于这个接口。

首先定义了两个菜品的接口:

//青椒炒蛋接口
interface QJCD {
  name: string;
  getName(): string;
}

//霸王别姬的接口
interface BWBJ {
  name: string;
  getName(): string;
}

再对菜品进行具体的实现:

//辣的青椒炒蛋
class LQJCD implements QJCD {
  name = "辣的青椒炒蛋";

  getName() {
    return this.name;
  }
}

//不辣的青椒炒蛋
class BLQJCD implements QJCD {
  name = "不辣的青椒炒蛋";

  getName() {
    return this.name;
  }
}

//辣的霸王别姬
class LBWBJ implements BWBJ {
  name = "辣的霸王别姬";

  getName() {
    return this.name;
  }
}

//不辣的霸王别姬
class BLBWBJ implements BWBJ {
  name = "不辣的霸王别姬";

  getName() {
    return this.name;
  }
}

不同厨师使用不同的菜品实现,为了规范,先定义一个规范:

两个厨师都需要对这两个菜品进行实现,所以先定义一个接口,而具体创建实例的方法是一样的,所以可以create直接继承。

//工厂抽象
abstract class Factory {
  create(name: string) {
    if (name === "青椒炒蛋") {
      return this.createQJCD();
    } else if (name === "霸王别姬") {
      return this.createBWBJ();
    } else {
      throw new Error("这个菜做不了");
    }
  }
  abstract createQJCD(): QJCD;
  abstract createBWBJ(): BWBJ;
}

两个厨师:

//A厨师
class ACookFactory extends Factory {
  createQJCD() {
    return new LQJCD();
  }

  createBWBJ() {
    return new LBWBJ();
  }
}

//b厨师
class BCookFactory extends Factory {
  createQJCD() {
    return new BLQJCD();
  }

  createBWBJ() {
    return new BLBWBJ();
  }
}

现在具体的厨师也有了,我们需要一个类来统一管理这个厨师,比如有的人要不辣的,我就抛出不辣的厨师。

//湘菜工厂的工厂
class XCFactoryFactory {
  static getFactory(L: boolean): Factory {
    if (L) {
      return new ACookFactory();
    } else {
      return new BCookFactory();
    }
  }
}

最终通过管理调用:

//管理员
class KitchenManager {
  //湘菜
  public xc(foodName: string, L: boolean) {
    const factory: Factory = XCFactoryFactory.getFactory(L);

    const food = factory.create(foodName);

    return food.getName();
  }
}

抽象工厂可以简单高效的创建多个产品与多个产品等级结构的问题,或者拿官话来讲:简单高效的实现了多个产品族与产品等级结构创建问题。

但是我们再细品代码:

当我们新增一个品类的时候,除了:青椒炒蛋,霸王别姬;我们还需要新增一个冬笋炒肉,此时你得在工厂抽象中声明抽象,声明之后子类实现上也得写一份具体实现,也就是说A、B两个厨师都得实现这个方法。增加了系统理解难度。

使用抽象工厂模式一般要满足以下条件。

  • 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
  • 系统一次只可能消费其中某一族产品,即同族的产品一起使用。

由于抽象工厂比较难理解,但是它又是面试官喜欢问的一个问题,当他问你工厂模式的时候,大概率问的就是抽象工厂模式,等有机会我在弄几个实战例子加深理解。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 1
文章被阅读 539

相关文章