各位观众老爷,大家好!我是你们的老朋友,今天咱们来聊聊JavaScript里的“工厂模式”。这玩意儿听起来像个流水线,实际上也差不多,只不过流水线上生产的是实体商品,而我们这里生产的是JavaScript对象。
为啥要用工厂模式?
话说回来,直接new
一个对象不香吗?为啥要搞这么复杂?答案是:为了解耦,为了更灵活,为了让你的代码更健壮,更易于维护。
想象一下,你正在开发一个游戏,里面有很多种怪物:史莱姆、哥布林、骷髅兵等等。每个怪物都有自己的属性和行为。如果你直接在代码里new Slime()
、new Goblin()
、new Skeleton()
,那代码就会变得非常冗余,而且以后如果想新增一种怪物,或者修改某种怪物的创建方式,那就要改动很多地方,想想就头疼。
工厂模式就是来解决这个问题的。它可以把对象的创建逻辑封装起来,让你只需要告诉工厂“我想要一个史莱姆”,工厂就会帮你创建一个史莱姆对象,而你不需要关心史莱姆是怎么被创建的。
工厂模式的几种实现方式
工厂模式有很多种实现方式,咱们今天主要讲三种:简单工厂模式、工厂方法模式和抽象工厂模式。
1. 简单工厂模式(Simple Factory Pattern)
这是最简单的一种工厂模式,也叫静态工厂模式。它只有一个工厂类,根据传入的参数来决定创建哪个对象。
// 怪物基类
class Monster {
constructor(name, health, attack) {
this.name = name;
this.health = health;
this.attack = attack;
}
attackTarget(target) {
console.log(`${this.name} attacks ${target.name} for ${this.attack} damage!`);
target.health -= this.attack;
if(target.health <= 0){
console.log(`${target.name} is defeated!`)
}
}
}
// 史莱姆类
class Slime extends Monster {
constructor() {
super('Slime', 50, 5);
}
split() {
console.log('Slime splits into two smaller slimes!');
}
}
// 哥布林类
class Goblin extends Monster {
constructor() {
super('Goblin', 80, 10);
}
steal(target) {
console.log(`Goblin steals from ${target.name}!`);
}
}
// 骷髅兵类
class Skeleton extends Monster {
constructor() {
super('Skeleton', 100, 15);
}
rattle() {
console.log('Skeleton rattles its bones!');
}
}
// 简单工厂类
class SimpleMonsterFactory {
static createMonster(type) {
switch (type) {
case 'slime':
return new Slime();
case 'goblin':
return new Goblin();
case 'skeleton':
return new Skeleton();
default:
throw new Error('Invalid monster type!');
}
}
}
// 使用工厂创建怪物
const slime = SimpleMonsterFactory.createMonster('slime');
const goblin = SimpleMonsterFactory.createMonster('goblin');
const skeleton = SimpleMonsterFactory.createMonster('skeleton');
slime.attackTarget(goblin);
goblin.attackTarget(skeleton);
console.log(goblin);
console.log(skeleton);
在这个例子中,SimpleMonsterFactory
就是一个简单工厂类。它有一个静态方法createMonster
,根据传入的type
参数来创建不同类型的怪物对象。
优点:
- 简单易懂,容易实现。
- 将对象的创建逻辑集中到一个地方,方便管理。
缺点:
- 违反了开闭原则(Open/Closed Principle),如果要新增一种怪物,就需要修改工厂类的代码。
- 工厂类的职责过重,既要负责创建对象,又要负责判断创建哪个对象。
2. 工厂方法模式(Factory Method Pattern)
为了解决简单工厂模式的缺点,我们可以使用工厂方法模式。工厂方法模式定义了一个抽象工厂类,其中声明了一个抽象的工厂方法,用于创建对象。具体的工厂类实现这个抽象方法,创建具体的对象。
// 抽象怪物工厂类
class MonsterFactory {
createMonster() {
throw new Error('This method must be implemented by subclasses.');
}
}
// 史莱姆工厂类
class SlimeFactory extends MonsterFactory {
createMonster() {
return new Slime();
}
}
// 哥布林工厂类
class GoblinFactory extends MonsterFactory {
createMonster() {
return new Goblin();
}
}
// 骷髅兵工厂类
class SkeletonFactory extends MonsterFactory {
createMonster() {
return new Skeleton();
}
}
// 使用工厂创建怪物
const slimeFactory = new SlimeFactory();
const slime = slimeFactory.createMonster();
const goblinFactory = new GoblinFactory();
const goblin = goblinFactory.createMonster();
const skeletonFactory = new SkeletonFactory();
const skeleton = skeletonFactory.createMonster();
slime.attackTarget(goblin);
goblin.attackTarget(skeleton);
console.log(goblin);
console.log(skeleton);
在这个例子中,MonsterFactory
是一个抽象工厂类,它定义了一个抽象方法createMonster
。SlimeFactory
、GoblinFactory
和SkeletonFactory
是具体的工厂类,它们分别实现了createMonster
方法,用于创建不同类型的怪物对象。
优点:
- 符合开闭原则,如果要新增一种怪物,只需要新增一个具体的工厂类即可,不需要修改已有的代码。
- 每个工厂类只负责创建一种对象,职责单一。
缺点:
- 类的数量会增多,代码会变得比较复杂。
3. 抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式是工厂方法模式的进一步抽象。它定义了一个抽象工厂接口,用于创建一系列相关或相互依赖的对象。每个具体的工厂类实现这个接口,创建一组相关的对象。
想象一下,你的游戏现在有了不同的难度级别:简单、普通和困难。每个难度级别都有自己的一套怪物。简单级别的怪物比较弱,困难级别的怪物比较强。
// 抽象怪物工厂接口
class AbstractMonsterFactory {
createSlime() {
throw new Error('This method must be implemented by subclasses.');
}
createGoblin() {
throw new Error('This method must be implemented by subclasses.');
}
createSkeleton() {
throw new Error('This method must be implemented by subclasses.');
}
}
// 简单难度怪物工厂
class EasyMonsterFactory extends AbstractMonsterFactory {
createSlime() {
return new Slime('Easy Slime', 30, 3); // 更弱的史莱姆
}
createGoblin() {
return new Goblin('Easy Goblin', 60, 8); // 更弱的哥布林
}
createSkeleton() {
return new Skeleton('Easy Skeleton', 80, 12); // 更弱的骷髅兵
}
}
// 普通难度怪物工厂
class NormalMonsterFactory extends AbstractMonsterFactory {
createSlime() {
return new Slime();
}
createGoblin() {
return new Goblin();
}
createSkeleton() {
return new Skeleton();
}
}
// 困难难度怪物工厂
class HardMonsterFactory extends AbstractMonsterFactory {
createSlime() {
return new Slime('Hard Slime', 80, 8); // 更强的史莱姆
}
createGoblin() {
return new Goblin('Hard Goblin', 120, 15); // 更强的哥布林
}
createSkeleton() {
return new Skeleton('Hard Skeleton', 150, 20); // 更强的骷髅兵
}
}
// 修改Monster类以接受自定义的name,health,attack
class Monster {
constructor(name, health, attack) {
this.name = name;
this.health = health;
this.attack = attack;
}
attackTarget(target) {
console.log(`${this.name} attacks ${target.name} for ${this.attack} damage!`);
target.health -= this.attack;
if(target.health <= 0){
console.log(`${target.name} is defeated!`)
}
}
}
// 史莱姆类
class Slime extends Monster {
constructor(name = 'Slime', health = 50, attack = 5) {
super(name, health, attack);
}
split() {
console.log('Slime splits into two smaller slimes!');
}
}
// 哥布林类
class Goblin extends Monster {
constructor(name = 'Goblin', health = 80, attack = 10) {
super(name, health, attack);
}
steal(target) {
console.log(`Goblin steals from ${target.name}!`);
}
}
// 骷髅兵类
class Skeleton extends Monster {
constructor(name = 'Skeleton', health = 100, attack = 15) {
super(name, health, attack);
}
rattle() {
console.log('Skeleton rattles its bones!');
}
}
// 使用工厂创建怪物
const easyFactory = new EasyMonsterFactory();
const easySlime = easyFactory.createSlime();
const easyGoblin = easyFactory.createGoblin();
const easySkeleton = easyFactory.createSkeleton();
const normalFactory = new NormalMonsterFactory();
const normalSlime = normalFactory.createSlime();
const normalGoblin = normalFactory.createGoblin();
const normalSkeleton = normalFactory.createSkeleton();
const hardFactory = new HardMonsterFactory();
const hardSlime = hardFactory.createSlime();
const hardGoblin = hardFactory.createGoblin();
const hardSkeleton = hardFactory.createSkeleton();
console.log(easySlime);
console.log(normalSlime);
console.log(hardSlime);
easySlime.attackTarget(normalGoblin);
normalGoblin.attackTarget(hardSkeleton);
console.log(normalGoblin);
console.log(hardSkeleton);
在这个例子中,AbstractMonsterFactory
是一个抽象工厂接口,它定义了创建史莱姆、哥布林和骷髅兵的方法。EasyMonsterFactory
、NormalMonsterFactory
和HardMonsterFactory
是具体的工厂类,它们分别实现了AbstractMonsterFactory
接口,用于创建不同难度级别的怪物。
优点:
- 可以创建一组相关的对象,保证它们之间的一致性。
- 符合开闭原则,如果要新增一种难度级别,只需要新增一个具体的工厂类即可,不需要修改已有的代码.
缺点:
- 类的数量会更多,代码会变得更加复杂。
- 如果要新增一种新的怪物类型,需要修改所有的抽象工厂和具体工厂类,维护成本较高。
三种工厂模式的对比
为了方便大家理解,我把这三种工厂模式的特点总结在一个表格里:
特点 | 简单工厂模式 | 工厂方法模式 | 抽象工厂模式 |
---|---|---|---|
优点 | 简单易懂,容易实现 | 符合开闭原则,职责单一 | 可以创建一组相关的对象,保证它们之间的一致性,符合开闭原则 |
缺点 | 违反开闭原则,工厂类的职责过重 | 类的数量会增多,代码会变得比较复杂 | 类的数量会更多,代码会变得更加复杂,如果要新增一种新的怪物类型,维护成本较高 |
适用场景 | 对象类型比较少,对象的创建逻辑比较简单 | 对象类型比较多,需要灵活扩展 | 需要创建一组相关的对象,并且需要灵活扩展 |
是否易于扩展 | 较差 | 较好 | 更好 |
选择哪种工厂模式?
选择哪种工厂模式取决于你的具体需求。一般来说,如果对象类型比较少,对象的创建逻辑比较简单,那么可以选择简单工厂模式。如果对象类型比较多,需要灵活扩展,那么可以选择工厂方法模式。如果需要创建一组相关的对象,并且需要灵活扩展,那么可以选择抽象工厂模式。
工厂模式的应用场景
除了上面提到的游戏开发,工厂模式还可以在很多其他场景中使用,比如:
- GUI框架: 可以使用工厂模式来创建不同类型的控件,比如按钮、文本框、下拉列表等等。
- 数据库访问: 可以使用工厂模式来创建不同类型的数据库连接,比如MySQL连接、Oracle连接、SQL Server连接等等。
- 日志记录: 可以使用工厂模式来创建不同类型的日志记录器,比如控制台日志记录器、文件日志记录器、数据库日志记录器等等。
总结
工厂模式是一种非常有用的设计模式,它可以帮助我们解耦代码,提高代码的灵活性和可维护性。虽然学习成本可能稍微高一些,但是一旦掌握了它,就能让你的代码更加优雅和健壮。希望今天的讲解能够帮助大家更好地理解和使用工厂模式。
最后,记住,设计模式不是银弹,不要为了使用模式而使用模式。只有在真正需要解决问题的时候,才应该考虑使用设计模式。好了,今天的讲座就到这里,谢谢大家!下次再见!