设计模式——工厂类设计模式(AI回答)

工厂类设计模式主要思路:不是调用者new真正干活的类,而是实现者在之间加了一个工厂类,实现者的工厂类来new真正干活的类。开发者只要new固定的工厂类?

你的理解非常准确。工厂模式的核心思想就是将"对象的创建"与"对象的使用"完全解耦

让调用者直接 new 真正干活的类(具体实现类),就像买东西直接去工厂生产线盯着一样,一旦生产线换了(类名改了、构造函数变了),调用者就得大面积修改代码。加了工厂类这个"中间商",调用者只需要跟中间商打交道即可。

至于你问的"开发者只要 new 固定的工厂类吗?",这个答案取决于你使用的是哪种"工厂模式"。工厂家族主要分三种,它们面对的场景和 new 的方式各有不同:

1. 简单工厂模式(Simple Factory)------ 确实是固定的工厂

如果你说的是"固定的工厂类",通常指的就是简单工厂

  • 怎么做 :整个系统只有一个固定的工厂类(比如 ShapeFactory)。

  • 怎么用 :开发者只需要 new 这个固定的工厂,或者直接调用它的静态方法,通过传入一个参数(如字符串或枚举)来决定要哪种产品。

  • 代码感觉

    复制代码
    // 开发者只需要和这一个固定工厂打交道
    Worker worker = SimpleFactory.createWorker("TypeA"); 
  • 局限 :如果新加了"真正干活的类",必须要修改这个固定工厂内部的 if-elseswitch 逻辑,违背了"开闭原则"(对扩展开放,对修改关闭)。

2. 工厂方法模式(Factory Method)------ 工厂本身也抽象了

为了解决简单工厂需要频繁修改工厂类的问题,工厂方法模式把工厂类自己也变成了一个接口或抽象类

  • 怎么做 :每一个"真正干活的类",都有一个与之对应的"专属工厂类"(比如 CatWorker 对应 CatWorkerFactoryDogWorker 对应 DogWorkerFactory)。

  • 怎么用 :这时候开发者不能new 一个固定的工厂了,而是要根据业务场景 new 对应的具体工厂。

  • 代码感觉

    复制代码
    // 想要 A 工人,得 new A 工厂
    WorkerFactory factory = new ConcreteWorkerFactoryA();
    Worker worker = factory.createWorker();
  • 意义 :虽然开发者要 new 不同的工厂,但由于工厂和产品都被抽象成了接口,后面的业务逻辑依然可以完全不关心具体实现。

3. 抽象工厂模式(Abstract Factory)------ 生产一整套产品族

当你的系统里"真正干活的类"不是孤立的,而是成套出现的(比如:Windows 风格的按钮和 Windows 风格的弹窗是一套;Mac 风格的按钮和 Mac 风格的弹窗是一套)。

  • 怎么做 :一个抽象工厂定义了生产一整套产品的方法。

  • 怎么用 :开发者在系统初始化时,选择 new 某一个具体的"套装工厂"(比如 MacFactory),之后整个系统拿到的所有组件都是这个套装里的。

  • 代码感觉

    复制代码
    // 一键切换成 Mac 套装工厂
    ThemeFactory factory = new MacThemeFactory(); 
    Button btn = factory.createButton(); // 自动获得 Mac 按钮
    Window win = factory.createWindow(); // 自动获得 Mac 窗口

总结与现代工程的演变

回到你的问题,你提到的"只 new 固定的工厂类"在简单工厂里表现最明显。

但在现代的企业级开发(例如使用 Spring 等控制反转/依赖注入框架)中,这个模式被推向了极致:开发者连工厂类都不需要自己去 new 。框架在底层充当了一个巨大的"终极工厂",开发者只需要通过注解(如 @Autowired@Resource)声明"我需要这个接口",框架就会自动把真正干活的类或者对应的工厂注入进来,连 new 工厂的动作都省了。

请详细解释工厂方法模式不用和用的区别

工厂方法模式详解

不用工厂方法时的问题

假设你在写一个游戏,地图上有不同种类的敌人:

复制代码
// 调用方直接 new 具体类
public class Game {
    public void spawnEnemy(String type) {
        if (type.equals("orc")) {
            Orc orc = new Orc();
            orc.init();
            orc.spawn();
        } else if (type.equals("troll")) {
            Troll troll = new Troll();
            troll.init();
            troll.spawn();
        }
        // 新增一种敌人?→ 必须来这里改代码
    }
}

问题很直接:Game 类知道了太多------它知道有 Orc、有 Troll,还知道怎么初始化每一种。每次新增敌人类型都要翻进来改 if/else,违反开闭原则,也让测试变得复杂。


用工厂方法后

第一步:定义产品接口

复制代码
public interface Enemy {
    void init();
    void spawn();
}

第二步:实现具体产品

复制代码
public class Orc implements Enemy {
    @Override
    public void init() {
        System.out.println("兽人初始化:装备斧头");
    }
    @Override
    public void spawn() {
        System.out.println("兽人出现在战场!");
    }
}

public class Troll implements Enemy {
    @Override
    public void init() {
        System.out.println("巨魔初始化:装备棍棒");
    }
    @Override
    public void spawn() {
        System.out.println("巨魔出现在战场!");
    }
}

第三步:定义抽象工厂(Creator)

复制代码
public abstract class EnemySpawner {

    // 工厂方法:声明但不实现,交给子类决定
    protected abstract Enemy createEnemy();

    // 业务逻辑只面向 Enemy 接口,不知道具体类型
    public void spawnEnemy() {
        Enemy enemy = createEnemy();  // 调工厂方法
        enemy.init();
        enemy.spawn();
    }
}

第四步:实现具体工厂(ConcreteCreator)

复制代码
public class OrcSpawner extends EnemySpawner {
    @Override
    protected Enemy createEnemy() {
        return new Orc();
    }
}

public class TrollSpawner extends EnemySpawner {
    @Override
    protected Enemy createEnemy() {
        return new Troll();
    }
}

第五步:调用方

复制代码
public class Game {
    public void startLevel(EnemySpawner spawner) {
        // 完全不知道、也不关心是哪种敌人
        spawner.spawnEnemy();
    }

    public static void main(String[] args) {
        Game game = new Game();
        game.startLevel(new OrcSpawner());
        game.startLevel(new TrollSpawner());
    }
}

输出:

复制代码
兽人初始化:装备斧头
兽人出现在战场!
巨魔初始化:装备棍棒
巨魔出现在战场!

现在新增一种龙(Dragon) ,只需添加两个新类,GameEnemySpawner 一行不动:

复制代码
public class Dragon implements Enemy {
    @Override
    public void init()  { System.out.println("龙初始化:张开翅膀"); }
    @Override
    public void spawn() { System.out.println("龙从天而降!"); }
}

public class DragonSpawner extends EnemySpawner {
    @Override
    protected Enemy createEnemy() { return new Dragon(); }
}

对比总结
不用工厂方法 用工厂方法
新增产品 修改已有 if/else 只新增两个类
调用方依赖 依赖所有具体类 只依赖抽象接口
测试 难以 mock 具体产品 注入任意 mock 工厂
职责 创建逻辑散落各处 集中在各自工厂类

优缺点

优点: 遵循开闭原则;调用方与具体产品解耦;每种产品的创建逻辑内聚在自己的工厂里,便于单独测试和替换。

缺点: 类的数量随产品种类线性增长,每增一种产品就多一个工厂类;如果产品种类少且不会变化,这套结构偏重,直接 new 反而更清晰。

请详细解释抽象工厂模式和工厂方法模式的区别

抽象工厂 vs 工厂方法

一句话区别
  • 工厂方法 :一个工厂,生产一种产品
  • 抽象工厂 :一个工厂,生产一族产品(多种产品,但风格统一)

用同一个场景来对比

还是游戏场景,但现在敌人不只是一个对象------每种阵营都有自己配套的士兵Boss,而且同一阵营的风格必须统一(不能兽人阵营用了精灵Boss)。


工厂方法的做法

工厂方法只能解决一种产品的创建。要创建士兵和Boss,就得建两套独立的工厂体系

复制代码
// 第一套:士兵工厂
abstract class SoldierSpawner {
    protected abstract Soldier createSoldier();
}

// 第二套:Boss工厂
abstract class BossSpawner {
    protected abstract Boss createBoss();
}

class OrcSoldierSpawner extends SoldierSpawner { ... }
class OrcBossSpawner   extends BossSpawner     { ... }

问题 :两套工厂完全独立,没有任何约束阻止你把 OrcSoldierSpawnerTrollBossSpawner 混着用------阵营错乱,代码靠自觉维护一致性。


抽象工厂的做法

抽象工厂把"同族产品"的创建收进同一个接口,从结构上保证一致性:

产品接口

复制代码
public interface Soldier {
    void attack();
}

public interface Boss {
    void rageAttack();
}

具体产品:兽人阵营

复制代码
public class OrcSoldier implements Soldier {
    public void attack() { System.out.println("兽人士兵:挥动斧头"); }
}

public class OrcBoss implements Boss {
    public void rageAttack() { System.out.println("兽人Boss:狂暴踩踏"); }
}

具体产品:精灵阵营

复制代码
public class ElfSoldier implements Soldier {
    public void attack() { System.out.println("精灵士兵:射出箭矢"); }
}

public class ElfBoss implements Boss {
    public void rageAttack() { System.out.println("精灵Boss:召唤风暴"); }
}

抽象工厂接口------这是核心,把一族产品绑在一起

复制代码
public interface FactionFactory {
    Soldier createSoldier();  // 创建该阵营的士兵
    Boss    createBoss();     // 创建该阵营的Boss
}

具体工厂:各阵营实现

复制代码
public class OrcFactory implements FactionFactory {
    public Soldier createSoldier() { return new OrcSoldier(); }
    public Boss    createBoss()    { return new OrcBoss();    }
}

public class ElfFactory implements FactionFactory {
    public Soldier createSoldier() { return new ElfSoldier(); }
    public Boss    createBoss()    { return new ElfBoss();    }
}

调用方------完全不知道具体阵营

复制代码
public class Game {
    private final Soldier soldier;
    private final Boss    boss;

    // 注入工厂,由工厂保证产品一族一致
    public Game(FactionFactory factory) {
        this.soldier = factory.createSoldier();
        this.boss    = factory.createBoss();
    }

    public void startBattle() {
        soldier.attack();
        boss.rageAttack();
    }

    public static void main(String[] args) {
        System.out.println("=== 兽人关卡 ===");
        new Game(new OrcFactory()).startBattle();

        System.out.println("=== 精灵关卡 ===");
        new Game(new ElfFactory()).startBattle();
    }
}

输出:

复制代码
=== 兽人关卡 ===
兽人士兵:挥动斧头
兽人Boss:狂暴踩踏
=== 精灵关卡 ===
精灵士兵:射出箭矢
精灵Boss:召唤风暴

新增亡灵阵营?新增 UndeadSoldierUndeadBossUndeadFactory 三个类,其余代码零修改。


结构对比图
复制代码
工厂方法                    抽象工厂
─────────────────           ──────────────────────────
EnemySpawner                FactionFactory
  └─ createEnemy()            ├─ createSoldier()
                              └─ createBoss()
OrcSpawner                  OrcFactory
  └─ createEnemy()→Orc        ├─ createSoldier()→OrcSoldier
                              └─ createBoss()→OrcBoss
TrollSpawner                ElfFactory
  └─ createEnemy()→Troll      ├─ createSoldier()→ElfSoldier
                              └─ createBoss()→ElfBoss

一个工厂方法 = 1×1          一个抽象工厂 = 1×N
(1工厂,1产品)             (1工厂,N种产品,保证同族)

核心差异总结
工厂方法 抽象工厂
解决的问题 一种产品的创建解耦 一族相关产品的创建解耦
工厂接口方法数 1个 createXxx() 多个 createXxx()
产品一致性 无约束 由工厂接口从结构上保证
扩展新产品种类 新增一对类即可 需修改工厂接口(破坏性变更)
扩展新产品族 --- 新增一个工厂实现即可
适用场景 产品种类单一,需要灵活替换 产品成族出现,必须保持风格统一

最后一点值得注意 :抽象工厂的弱点是横向扩展困难 ------如果要给所有阵营新增一种产品(比如加"坐骑"),就必须修改 FactionFactory 接口,所有具体工厂都得跟着改。工厂方法没有这个问题,因为每种产品的工厂体系互相独立。两种模式本质上是在"纵向扩展(加族)"和"横向扩展(加品类)"之间做权衡。