工厂方法模式与组合

Factory Method pattern vs composition

来自 GoF 关于工厂方法模式的章节:

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

思路是让subclasses决定实例化哪个class,而GoF的实现思路,就是在superclass中提供一个抽象方法,即subclasses需要覆盖,从而提供自己的实例化逻辑。

我不明白的是,为什么要使用抽象方法来实现它,而不是使用 工厂的 class 成员。我来解释一下。

这是 GoF 的方式:

public abstract class Creator {
    public void doStuff() {
        Product product = createProduct();
        /* etc. */
    }

    public abstract Product createProduct();
}

用法:

    MyCreator myCreator = new Creator() {
        @Override
        public Product createProduct() {
            return new MyProduct();
        }
    }

    myCreator.doStuff();

但是为什么要为 subclassing 等烦恼呢?我建议这种方法:

public class Creator {
    private Factory factory;

    /* constructor */
    public Creator(Factory factory) {
        this.factory = factory;
    }

    public void doStuff() {
        Product product = factory.createProduct();
        /* etc. */
    }
}

这就是您的使用方式:

    MyFactory myFactory = new MyFactory();
    Creator creator = new Creator(myFactory);
    creator.doStuff();

那么 GoF 方法的好处是什么?为什么不把Factory组合成Creatorclass,而是用抽象方法表达呢?

Why not composing the Factory into the Creator class instead of expressing it using an abstract method?

因为在那种情况下,客户端 "knows" 实例化什么工厂 class 会破坏模式的全部目的。整个想法是客户不知道工厂是什么,它只调用抽象 class。认为您将某个第三方工厂提供商插入到您的框架中,并且您不知道他们实例化(也不关心)什么,只要能达到目的。

在这些类型的情况下,使用实际的英语类型进行说明通常很有用,而不是像 "Product"

这样更抽象的术语

所以,让我们以宠物为例,假设每种(子)类型的宠物都有一个家(例如,狗有一个狗窝)。

对于您的示例,我们将有:

Public void doStuff() {
    Home home = factory.createHome();
}

但是在这种情况下,说狗得到狗舍而鸟得到笼子的逻辑在哪里?它必须进入工厂对象——这反过来意味着 Pets 的继承层次结构必须在工厂逻辑中明确重复。这反过来意味着每次添加新类型的宠物时,您都必须记住将该类型也添加到工厂中。

工厂方法模式意味着你会有类似的东西:

public class Pet {
    private String name;
    private Home home;
    public Pet(String name) {
        this.name = name;
        this.home = this.createHome();
    }
    public abstract Home createHome();

这允许您每次创建新类型的宠物时,都可以指定该宠物类型的家而无需更改任何其他类型 class,并且在此过程中还知道每个宠物类型都有一个家。

例如。你会:

public class Dog extends Pet {
    public Home createHome() {
        return new Kennel();
    }
}

编辑以添加狗示例

工厂方法模式用于让子classes 控制它们生成的对象类型。在您的情况下,控制对象类型的不是 subclass,而是 class.

的用户

以下示例是从the Wikipedia entry on the Factory Method Pattern 复制的。我们有一个迷宫游戏,它有两种变体:普通迷宫游戏和修改规则的魔法迷宫游戏。普通迷宫游戏由普通房间和魔法房间的魔法迷宫游戏组成

public class MazeGame {
    public MazeGame() {
        Room room1 = makeRoom();
        Room room2 = makeRoom();
        room1.connect(room2);
        this.addRoom(room1);
        this.addRoom(room2);
    }

    protected Room makeRoom() {
        return new OrdinaryRoom();
    }
}

public class MagicMazeGame extends MazeGame {
    @Override
    protected Room makeRoom() {
        return new MagicRoom();
    }
}

通过使用工厂方法模式,我们可以确保 MagicMazeGame 由 MagicRooms 组成,但我们仍然可以重复使用部分 superclass.

根据您的建议,MazeGame class 将更改为:

public class MazeGame {
    public MazeGame(RoomFactory factory) {
        Room room1 = factory.makeRoom();
        Room room2 = factory.makeRoom();
        room1.connect(room2);
        this.addRoom(room1);
        this.addRoom(room2);
    }
}

现在可以做:

MazeGame game1 = new MazeGame(new MagicRoomFactory());
MazeGame game2 = new MagicMazeGame(new OrdinaryRoomFactory());

这是您希望避免的事情,因为这样可以创建包含错误房间类型的迷宫游戏。

我认为这个问题的答案并不完全令人满意,所以这是我的两分钱...

引用@Hoopje 的回答,我们可能会得到这样的结果:

public class MazeGame {
    public MazeGame(RoomFactory factory) {
        Room room1 = factory.makeRoom();
        Room room2 = factory.makeRoom();
        room1.connect(room2);
        this.addRoom(room1);
        this.addRoom(room2);
    }
}

然后

MazeGame game1 = new MazeGame(new MagicRoomFactory());
MazeGame game2 = new MagicMazeGame(new OrdinaryRoomFactory());

如上所述,这是荒谬的。但是,我认为最后两行无论如何都没有意义,因为在 OP 看来,您根本不需要创建 MagicMazeGame。相反,您只需实例化一个传入 new MagicRoomFactory() 的 MazeGame。

MazeGame magicOne = new MazeGame(new MagicRoomFactory());
MazeGame normalOne = new MazeGame(new OrdinaryRoomFactory());

但是,假设我们的 MazeGame 有多个工厂。比方说,除了需要有一个房间工厂,它还需要有一个积分工厂,还有一个怪物工厂。在这种情况下,我们最终可能会将所有这些都强制放入 MazeGame 构造函数中,如下所示:

public MazeGame(RoomFactory rmFactory, PointsFactory pointsFactory, MonsterFactory monstersFactory) 

这相当复杂,最好创建具体的 sub类 来控制自己的游戏类型。那个长的构造函数对我来说也像是一种代码味道,尽管更有知识的人可能会添加到这个。

更重要的是,这意味着无论哪个对象正在实例化游戏,都必须了解所有这些内部工作 类,这会破坏封装。 总而言之,通过这种方式我们给了客户太多的控制权,他们不想知道所有这些实现细节。

反正我是这么看的...