关于Java:工厂方法模式vs组合

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.

其思想是让子类决定要实例化哪个类,而GOF的思想实现是在超类中提供一个抽象方法,子类需要重写,从而提供自己的实例化逻辑。

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

这是GOF方式:

1
2
3
4
5
6
7
8
public abstract class Creator {
    public void doStuff() {
        Product product = createProduct();
        /* etc. */
    }

    public abstract Product createProduct();
}

用途:

1
2
3
4
5
6
7
8
    MyCreator myCreator = new Creator() {
        @Override
        public Product createProduct() {
            return new MyProduct();
        }
    }

    myCreator.doStuff();

但是为什么要费心子类化等呢?我建议这种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Creator {
    private Factory factory;

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

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

这就是你如何使用它:

1
2
3
    MyFactory myFactory = new MyFactory();
    Creator creator = new Creator(myFactory);
    creator.doStuff();

那么,GoF的方法有什么好处呢?为什么不将工厂组成Creator类而不是使用抽象方法来表示它呢?


工厂方法模式用于给子类控制它们生成的对象的类型。在您的例子中,控制对象类型的不是子类,而是类的用户。

下面的示例是工厂方法模式的维基百科条目的副本。我们有一个迷宫游戏,它有两个变体:普通迷宫游戏和一个有适应规则的魔法迷宫游戏。普通迷宫游戏包括普通房间和魔术房间的魔术迷宫游戏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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由magicRoom组成,但是我们仍然可以重用超类的部分。

在您的建议中,mazegame类将改为:

1
2
3
4
5
6
7
8
9
public class MazeGame {
    public MazeGame(RoomFactory factory) {
        Room room1 = factory.makeRoom();
        Room room2 = factory.makeRoom();
        room1.connect(room2);
        this.addRoom(room1);
        this.addRoom(room2);
    }
}

现在有可能做到:

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

这是你想要避免的事情,因为这样就可以创建一个有错误房间的迷宫游戏。


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

因为在这种情况下,客户机"知道"要实例化的工厂类,这会破坏模式的整个目的。整个想法是,客户机不知道什么工厂是建立的,它只调用一个抽象类。假设您将某个第三方工厂提供程序插入到您的框架中,并且您不知道它们实例化了什么(也不关心),只要满足了目的。


在这种情况下,使用实际的英语类型来做插图,而不是像"产品"这样更抽象的术语,通常是有用的。

所以,让我们拿宠物来说,每一种(亚)类型的宠物都有一个家(例如,狗有一个犬舍)。

对于您的示例,我们将:

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

但在这种情况下,说狗得到狗窝,鸟得到笼子的逻辑去了哪里?它必须进入工厂对象——这意味着宠物的继承层次结构必须在工厂逻辑中明确地重复。这反过来意味着,每次你添加一种新类型的宠物,你将不得不记住添加到工厂的类型以及。

工厂方法模式意味着您将拥有如下内容:

1
2
3
4
5
6
7
8
public class Pet {
    private String name;
    private Home home;
    public Pet(String name) {
        this.name = name;
        this.home = this.createHome();
    }
    public abstract Home createHome();

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

你应该有:

1
2
3
4
5
public class Dog extends Pet {
    public Home createHome() {
        return new Kennel();
    }
}

编辑以添加狗示例