木灵鱼儿
阅读:132
创建型模式:工厂模式(包含抽象工厂)
简介
工厂模式是讲需要实例化的代码提取出来,放到一个专门的类中去做管理和维护,达到和主体代码的解耦,从而提高项目的扩展性和维护性。将创建实例与调用实例进行解耦!
简单来说就是把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两个厨师都得实现这个方法。增加了系统理解难度。
使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
由于抽象工厂比较难理解,但是它又是面试官喜欢问的一个问题,当他问你工厂模式的时候,大概率问的就是抽象工厂模式,等有机会我在弄几个实战例子加深理解。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
行为模式:观察者模式
简介观察者模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。而我们前端vue的响应式原理其实就是观察者模式的实现,只不过他是完全解耦的发布订阅者模式;和基础的观察者模式实现上会有不同,原理都是一样的。观察者模式是为了将重要的部分与辅助部分进行解耦,我们举个例子:在前端中,我们的滚动条距离就可以理解为一个重要部分,我们监听了scroll事件,并在回调函数中处理了一条特殊操作,比如根据滚动的距离操作header元素显隐。此时这个代码量很少,我们知道scroll事件是不建议监听太多的,...
行为模式:迭代器模式
简介迭代器模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。简单来说就是再程序设计中,对于一些自定义的数据结构对象,比如链表啊,或者系统提供的数据结构对象,他们的遍历方法通常会写在数据结构的类中,也就是说数据和遍历的方法是写在同一个类里面。这种方式不利于程序的扩展,因为如果要改动遍历的方法,就必须改动源数据,这违背了开闭原则。既然遍历的方法不能写在数据类中,那么将遍历的方法让客户实现不就好了,但是同样也是不可取的,如果交由客户去写,第一是会暴露很多不必要的属性给客户,第二是增加了客户端的使用负担。而迭代器模式很好的解决了这个问题,在迭代器模式中有以下几个角色...
行为模式:访问者模式
简介访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。有一点点抽象,前一句话的意思是指在写好一个类之后,这个类就基本上不需要改动了(只要需求不改),这其实就是为了解决类结构不变但操作处理逻辑易变的问题,把对数据的操作都封装到访问者类中,我们只需要调用不同的访问者,而无需改变改变结构类,实现了。简单来说就是将一些操作作为扩展分离出去, 这些扩展的功能可能现在没有,以后会有,我们无法知道以后的情况,于是...
行为模式:命令模式
简介命令模式是将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。举个简单的例子:我有一个饭店,共有五个厨师:A,B,C,D,E;每个厨师都会做不同的菜,但是我没有菜单,于是客人就必须知道我有几个厨师,他们分别会做什么菜,于是就会产生下面这些情况:客人1想吃青椒炒蛋,于是问了好几个厨师才知道C厨师会做,于是它通知C厨师做青椒炒蛋客人2想吃冒菜,于是他也问了好几个厨师,找到E厨师制作...更多你会发现客人与厨师耦合度非常之高,这显然不利于饭店的发展,然后我们想一下生活中的真实情况:每个饭店都...
行为模式:模板方法模式
简介模板方法模式:定义一个操作中的算法骨骼,而将算法的一些步骤延迟到子类中去,使得子类可以不改变整体算法结构的情况下重新定义该算法的某些特定步骤。简单点来说就是将重要的步骤顺序的处理交由父类来做,然后父类是一个抽象的类,它申明了每个步骤的抽象,由子类去进行每个步骤的具体实现。这样父类定好了一个整体的骨骼,具体步骤实现交给子类,每当需要改动时只需要更换不同的子类就可以了,符合开闭原则。代码实现//抽象父类 abstract class A { //抽象方法 public abstract step1(): void; //抽象方法 public abstract step...
结构型模式:代理模式
简介代理模式指:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。更直白的说,代理模式更注重的是对源对象的控制访问(中介和保护),虽然我们可以在代理对象上扩展源对象没有的属性,但是如果要增强原有的功能其实还需要和其他模式进行组合使用,比如装饰器模式。角色:抽象角色:用于源对象与代理对象的依赖抽象源对象:真实的业务对象,是代理类所要代表的真实对象代理类:通过抽象角色规范,提供与源对象相同的接口,内部含有对源对象的引用,通过它可以进行扩展和控制代码实现//抽象 interface AInterfa...
结构型模式:享元模式
简介享元模式是运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。举个例子:我们有一个服装工厂,它有男装和女装衣服各100套,为了销售,需要模特来帮忙拍照做成广告照片,正常情况下我们可能会这么写。class Model { private gender: string; //性别 private clothes: number; //衣服 constructor(gender: string, clothes: number) { this.gender = gende...
结构型模式:外观模式
简介外观模式:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。其实就是一种封装,把一系列的操作封装成简单的接口供客户端去调用。但是不能滥用,因为本身就是为了减少客户端与各个依赖的耦合性,将一堆依赖改为依赖一个外观类。外观模式遵循的是迪米特法则,也就是最少知道原则,但是如果新增新的功能,可能会改动到外观类,所以它违背了开闭原则。它常常应用于SKD,开源类库这些,用于给外部提供精简的接口。而我们js,可能并不需要去创建一个类,直接整个函...

结构型模式:组合模式
简介组合模式它本身是一种树形结构模式,可以将其理解为一棵树,其中树为根节点,然后有树枝和树叶,树枝不断的分叉,树枝上也会有树叶。我们看个图:它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。其实说人话就是,我定义了一个接口,它有一个show方法,然后所有的对象都得实现,然后对象之间可以嵌套,因为嵌套了,所以当我调用show方法的时候,这个对象必须去遍历自己的子级嵌套对象的show方法,不断的重复,最终得到结果之和。类似于一个递归函数,只不过具体的处理都在每个对象自身show方法中去实现。组合模式常常用在:购物车、节点统计购...

结构型模式:装饰器模式
简介在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。装饰器模式指:在不改变现有对象结构的情况下,动态的给对象增加一些职责(功能)的一种模式。这种模式也是防止滥用继承,相对于桥接模式只能桥接两个维度,装饰器模式是可以对接多个维度的,因为他可以嵌套使用。我们举个例子:我有一个食物类:class 煎饼我单点煎饼这个食物是可以的,我也可以加不同的配料:鸡蛋、火腿、豆皮于是乎我们通过继承得到:class 鸡蛋煎饼 extends 煎饼class 火腿煎饼 extends 煎饼class...