【问题标题】:Factory Method pattern vs composition工厂方法模式与组合
【发布时间】:2015-02-27 14:42:57
【问题描述】:

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

定义创建对象的接口,但让子类决定创建哪个类 实例化。工厂方法允许类将实例化推迟到子类。

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

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

这是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();

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

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 组合到 Creator 类中,而不是使用抽象方法来表达它?

【问题讨论】:

  • 这是因为你想在上面的抽象类中的所有具体工厂之间共享公共代码。现在您所做的是正确的,但是如何在您的工厂之间共享/继承公共代码?因此使用抽象类更加灵活。

标签: java design-patterns factory-pattern


【解决方案1】:

为什么不将 Factory 组合到 Creator 类中,而不是使用抽象方法来表达呢?

因为在这种情况下,客户端“知道”要实例化哪个 Factory 类,这违背了模式的全部目的。整个想法是客户端不知道构建了什么工厂,它只调用抽象类。认为您将某些第三方工厂提供商插入到您的框架中,并且您不知道他们实例化了什么(并且也不关心)只要服务于目的。

【讨论】:

  • 那是一个错误的答案。客户总是知道混凝土工厂。它只是不知道正在创建的具体产品。
【解决方案2】:

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

以下示例是来自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 组成,但我们仍然可以重用超类的部分内容。

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

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());

这是您想要阻止的事情,因为这样可以创建带有错误房间类型的 MazeGames。

【讨论】:

  • 为什么我们需要防止这种情况发生?我的意思是代码的最后一部分
  • @Tony Lin:因为 MazeGame 中的房间类型会错误。 (我编辑了答案以使这一点更清楚。)
【解决方案3】:

在这些类型的情况下,使用实际的英文类型来进行插图而不是像“产品”这样更抽象的术语通常很有用

所以,让我们以 Pets 为例,假设 Pet 的每个(子)类型都有一个 Home(例如,Dog 有一个 Kennel)。

对于您的示例,我们将:

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();

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

例如。你会有:

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

编辑添加狗示例

【讨论】:

    【解决方案4】:

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

    引用@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) 
    

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

    更重要的是,这意味着任何实例化游戏的对象都必须了解所有这些内部工作类,这会破坏封装。 总而言之,通过这种方式,我们将过多的控制权交给了不想了解所有这些实施细节的客户。

    反正我是这么看的……

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-11
      • 2014-05-03
      • 2020-10-25
      • 2014-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-30
      相关资源
      最近更新 更多