在对象内部使用工厂方法而不是使用工厂类是什么时候一个好主意?
在对象内部使用工厂方法而不是使用工厂类是什么时候一个好主意?
我喜欢将设计模式看作是我的类“人员”,而这些模式则是人们相互交流的方式。
因此,对我来说,工厂模式就像一个招聘机构。你有一个需要不同数量工人的人。这个人可能知道他们需要雇佣的人的一些信息,但也只是如此。
所以,当他们需要新员工时,他们会给招聘机构打电话告诉他们需要什么。现在,实际上要“雇用”某人,你需要了解很多东西——福利、资格验证等等。但是招聘者不需要知道这些——招聘机构会处理所有事情。
同样地,使用工厂模式允许消费者创建新对象,而无需知道它们的创建细节或依赖关系——他们只需要提供他们真正想要的信息即可。
public interface IThingFactory
{
Thing GetThing(string theString);
}
public class ThingFactory : IThingFactory
{
public Thing GetThing(string theString)
{
return new Thing(theString, firstDependency, secondDependency);
}
}
现在,ThingFactory的使用者可以获取一个Thing,而无需了解Thing的依赖项,除了来自使用者的字符串数据之外。
当构造函数表达力不够时,应将工厂方法视为构造函数的替代品。
class Foo{
public Foo(bool withBar);
}
不如表达力强:
class Foo{
public static Foo withBar();
public static Foo withoutBar();
}
当您需要一个复杂的对象构建过程时,工厂类是非常有用的;当构建过程需要某个实际类不需要的依赖项时;当您需要构建不同的对象时等。
个人认为使用单独的工厂类有意义的情况是,当你创建的最终对象依赖于其他多个对象。例如在PHP中:假设你有一个House
对象,它又有一个Kitchen
和一个LivingRoom
对象,而LivingRoom
对象里面还有一个TV
对象。
实现这个的最简单方法是让每个对象在它们的构造方法中创建它们的子对象,但如果属性比较嵌套,当你的House
创建失败时,你可能会花费一些时间来确定具体是哪里出了问题。
另一种选择是采用以下方法(如果您喜欢花哨的术语,可以称之为依赖注入):
$TVObj = new TV($param1, $param2, $param3);
$LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
$KitchenroomObj = new Kitchen($param1, $param2);
$HouseObj = new House($LivingroomObj, $KitchenroomObj);
如果创建 House
的过程失败,那么只有一个地方需要查看,但是每次想要新建一个 House
时都必须使用这个代码块,这显然很不方便。于是引入了工厂模式:
class HouseFactory {
public function create() {
$TVObj = new TV($param1, $param2, $param3);
$LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
$KitchenroomObj = new Kitchen($param1, $param2);
$HouseObj = new House($LivingroomObj, $KitchenroomObj);
return $HouseObj;
}
}
$houseFactory = new HouseFactory();
$HouseObj = $houseFactory->create();
感谢这里的工厂,创建一个House
的过程被抽象化了(这意味着当您想要创建一个House
时,您不需要创建和设置每个单独的依赖项),同时也是集中化的,这使得维护更容易。使用单独的工厂有其他好处(例如可测试性),但我发现这个特定的用例最能说明工厂类的有用之处。
create
方法。例如,如果你的 House
总是拥有相同类型的 LivingRoom
,那么在工厂类中将其参数硬编码可能比作为参数传递更有意义。或者,如果你有几种不同类型的 LivingRoom
并且在内部使用 switch 语句,则可以为 HouseFactory::create
方法提供一个 type
参数,并为每种类型硬编码参数。 - Mahn当以下情况发生时,在对象内部使用工厂方法是一个好主意:
当以下情况发生时,使用抽象工厂类是一个好主意:
明确区分使用工厂或工厂方法的概念非常重要,它们都旨在解决互不相同的对象创建问题。
让我们具体说明“工厂方法”:
首先,当您正在开发库或API时,这些库或API将被用于进一步的应用程序开发,那么工厂方法是用于创建模式的最佳选择之一。原因是:我们知道何时创建所需功能的对象,但对象类型仍未确定或将通过传递的动态参数确定。
现在问题是,使用工厂模式可以实现类似的效果,但如果将工厂模式用于上述突出问题,则会引入一个巨大的缺点,即创建不同对象(子类对象)的逻辑将特定于某些业务条件。因此,将来如果需要为其他平台扩展库的功能(更技术上地说,需要添加基本接口或抽象类的更多子类,以便工厂根据某些动态参数返回这些对象),那么每次都需要更改(扩展)工厂类的逻辑,这将是一项昂贵的操作,并且从设计角度来看也不好。
另一方面,如果使用“工厂方法”模式执行相同的操作,那么您只需要创建附加功能(子类)并通过注入动态地将其注册,而无需更改基本代码。
interface Deliverable
{
/*********/
}
abstract class DefaultProducer
{
public void taskToBeDone()
{
Deliverable deliverable = factoryMethodPattern();
}
protected abstract Deliverable factoryMethodPattern();
}
class SpecificDeliverable implements Deliverable
{
/***SPECIFIC TASK CAN BE WRITTEN HERE***/
}
class SpecificProducer extends DefaultProducer
{
protected Deliverable factoryMethodPattern()
{
return new SpecificDeliverable();
}
}
public class MasterApplicationProgram
{
public static void main(String arg[])
{
DefaultProducer defaultProducer = new SpecificProducer();
defaultProducer.taskToBeDone();
}
}
当你需要相同参数类型但有不同行为的多个“构造函数”时,它们也非常有用。
代码片段:
import java.util.HashMap;
/* Product interface as per UML diagram */
interface Game{
/* createGame is a complex method, which executes a sequence of game steps */
public void createGame();
}
/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game{
public Chess(){
}
public void createGame(){
System.out.println("---------------------------------------");
System.out.println("Create Chess game");
System.out.println("Opponents:2");
System.out.println("Define 64 blocks");
System.out.println("Place 16 pieces for White opponent");
System.out.println("Place 16 pieces for Black opponent");
System.out.println("Start Chess game");
System.out.println("---------------------------------------");
}
}
class Checkers implements Game{
public Checkers(){
}
public void createGame(){
System.out.println("---------------------------------------");
System.out.println("Create Checkers game");
System.out.println("Opponents:2 or 3 or 4 or 6");
System.out.println("For each opponent, place 10 coins");
System.out.println("Start Checkers game");
System.out.println("---------------------------------------");
}
}
class Ludo implements Game{
public Ludo(){
}
public void createGame(){
System.out.println("---------------------------------------");
System.out.println("Create Ludo game");
System.out.println("Opponents:2 or 3 or 4");
System.out.println("For each opponent, place 4 coins");
System.out.println("Create two dices with numbers from 1-6");
System.out.println("Start Ludo game");
System.out.println("---------------------------------------");
}
}
/* Creator interface as per UML diagram */
interface IGameFactory {
public Game getGame(String gameName);
}
/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory {
HashMap<String,Game> games = new HashMap<String,Game>();
/*
Since Game Creation is complex process, we don't want to create game using new operator every time.
Instead we create Game only once and store it in Factory. When client request a specific game,
Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
*/
public GameFactory(){
games.put(Chess.class.getName(),new Chess());
games.put(Checkers.class.getName(),new Checkers());
games.put(Ludo.class.getName(),new Ludo());
}
public Game getGame(String gameName){
return games.get(gameName);
}
}
public class NonStaticFactoryDemo{
public static void main(String args[]){
if ( args.length < 1){
System.out.println("Usage: java FactoryDemo gameName");
return;
}
GameFactory factory = new GameFactory();
Game game = factory.getGame(args[0]);
if ( game != null ){
game.createGame();
System.out.println("Game="+game.getClass().getName());
}else{
System.out.println(args[0]+ " Game does not exists in factory");
}
}
}
输出:
java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
Game=Chess
这个例子展示了通过实现FactoryMethod
来实现Factory
类。
Game
是所有类型游戏的接口。它定义了复杂的方法:createGame()
Chess, Ludo, Checkers
是不同类型的游戏变体,它们提供了createGame()
的实现。
public Game getGame(String gameName)
是IGameFactory
类中的FactoryMethod
GameFactory
在构造函数中预先创建了不同类型的游戏。它实现了IGameFactory
工厂方法。
游戏名称作为命令行参数传递给NotStaticFactoryDemo
GameFactory
中的getGame
接受一个游戏名称并返回相应的Game
对象。
定义来自Wikipedia/Gang of Four ( Design Patterns: Elements of Reusable Object-Oriented Software book)
工厂模式:创建对象而不向客户端公开实例化逻辑。
工厂方法:定义一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法允许类推迟它所使用的实例化到子类中。(四人帮)
使用情况:
何时使用:客户端
不知道在运行时需要创建哪些具体类。它只想得到一个能够完成工作的类。
getArea()
根本不是工厂方法。 - Cranio这实际上是个口味问题。工厂类可以根据需要抽象/接口化,而工厂方法更加轻量级(也更容易进行测试,因为它们没有定义类型,但需要一个众所周知的注册点,类似于服务定位器但用于定位工厂方法)。
当返回的对象类型具有私有构造函数,不同的工厂类在返回对象上设置不同的属性,或者特定的工厂类型与其返回的具体类型耦合时,工厂类非常有用。
WCF 使用 ServiceHostFactory 类在不同情况下检索 ServiceHost 对象。标准 ServiceHostFactory 由 IIS 用于检索 .svc 文件的 ServiceHost 实例,但是 WebScriptServiceHostFactory 用于向 JavaScript 客户端返回序列化的服务。ADO.NET 数据服务具有自己的特殊 DataServiceHostFactory,并且 ASP.NET 具有 ApplicationServicesHostFactory,因为其服务具有私有构造函数。
如果只有一个类正在使用工厂,则可以在该类内部使用工厂方法。