什么是静态工厂方法?

307

什么是“静态工厂”方法?


3
Jason Owen的回答存在缺陷,因为它声称在谈论工厂方法模式,而实际上它所讨论的是静态工厂方法模式。因此,虽然它对实际问题做出了很好的回答,但我认为它不能在目前的状态下被接受,因为它引入了一个不相关的模式,并增加了已经极为普遍的两种模式之间差异的混淆。 - Theodore Murdock
1
@CMCDragonkai 我认为依赖注入和静态工厂是不同的。即使在依赖注入的情况下,静态工厂也可能需要用于实例化要注入的依赖项。 - Karan Khanna
14个回答

522
静态工厂方法模式是一种封装对象创建的方式。如果没有工厂方法,你只能直接调用类的构造函数Foo x = new Foo()。使用这种模式,你应该调用工厂方法:Foo x = Foo.create()。构造函数被标记为私有,因此除了类内部,无法调用它们,而工厂方法被标记为static,因此可以在没有对象的情况下调用它。
这种模式有一些优点。其中之一是工厂可以从许多子类(或接口实现者)中选择并返回它。这样,调用者可以通过参数指定所需的行为,而不必知道或理解潜在的复杂类层次结构。
另一个优点是,正如Matthew和James所指出的那样,控制对诸如连接之类的有限资源的访问。这是实现可重用对象池的一种方式——如果构建和销毁是昂贵的过程,那么与其构建、使用和拆除对象,不如构建它们一次并进行回收利用。如果工厂方法有未使用的已实例化对象,则可以返回该对象;如果对象计数低于某个下限,则可以构造一个对象;如果对象计数高于上限,则可以抛出异常或返回null
根据维基百科上的文章,多个工厂方法还允许对相似参数类型进行不同的解释。通常构造函数与类名相同,这意味着您只能有一个具有特定签名的构造函数。工厂没有这样的限制,这意味着您可以拥有两个接受相同参数类型的不同方法:
Coordinate c = Coordinate.createFromCartesian(double x, double y)

Coordinate c = Coordinate.createFromPolar(double distance, double angle)

如 Rasmus 所指出的那样,这也可以用于提高可读性。


41
请注意,静态工厂方法与《设计模式》[Gamma95, p. 107]中的工厂方法模式不同。本条目中描述的静态工厂方法在设计模式中没有直接等价物。 - Josh Sunshine
1
这个答案对我来说最有价值的是关于可池化对象的提及。这正是我使用工厂方法模式的方式。工厂方法被提供来帮助控制对象的生命周期:“create”从池中获取对象或在池为空时创建一个新实例,“destroy”将其返回到池中以便未来重复使用。 - user656698
5
在您的帖子中无论何时提到“工厂方法模式”,您都应该真正使用“静态工厂方法模式”,因为您使用了错误的术语。此外,您链接到维基百科文章是关于不同模式而非您所讨论的模式。 工厂方法模式可能应该被称为“工厂接口”模式,因为它涉及使用多个实现工厂接口的工厂对象来使单个算法能够生成并处理接口实例,并接受一个可以生成特定所需子类的工厂对象。 - Theodore Murdock
2
请注意,构造函数不一定要是私有的。一个类可以同时提供公共静态工厂方法和构造函数。 - Kevin
1
请根据 @josh-sunshine、theodore-murdock 和 emerald214 给出的评论编辑您的答案。否则会让初学者感到困惑,请避免这种情况。谢谢。 - Snesh
显示剩余3条评论

186

注意!“静态工厂方法工厂方法模式不同” (c) Effective Java, Joshua Bloch。

工厂方法:“定义一个创建对象的接口,但让实现该接口的类来决定实例化哪个类。工厂方法让类推迟实例化到子类中进行” (c) GoF。

“静态工厂方法只是一个返回类实例的静态方法。” (c) Effective Java, Joshua Bloch。通常这个方法在特定的类中。

它们之间的区别:

静态工厂方法的关键思想是获得对对象创建的控制,并将其委托从构造函数转移到静态方法中。创建对象的决策,像在抽象工厂中一样,在方法之外(在常见情况下,但并非总是如此)。而工厂方法的关键(!)思想是将创建类实例的决策委托给工厂方法内部。例如,经典的单例模式就是静态工厂方法的一种特殊情况。常用的静态工厂方法示例如下:

  • valueOf
  • getInstance
  • newInstance

你能告诉我 new A() 和 A.newInstance() 之间的区别吗?在 A.newInstance 中我们应该做什么? - Shen
@Shen 通常当你有一个像newInstance这样的静态方法时,构造函数是私有的,因此你不能使用new A()创建对象,在这种情况下,你需要将newInstance方法设置为静态和公共,并用它返回新的A()对象。P.S. 我知道我回答晚了。 - Shubhankar Dimri

138

我们避免直接访问数据库连接,因为它们很耗资源。所以我们使用一个静态工厂方法getDbConnection来创建连接,如果我们低于限制,则创建连接。否则,它会尝试提供“备用”连接,如果没有,则失败并抛出异常。

public class DbConnection{
   private static final int MAX_CONNS = 100;
   private static int totalConnections = 0;

   private static Set<DbConnection> availableConnections = new HashSet<DbConnection>();

   private DbConnection(){
     // ...
     totalConnections++;
   }

   public static DbConnection getDbConnection(){

     if(totalConnections < MAX_CONNS){
       return new DbConnection();

     }else if(availableConnections.size() > 0){
         DbConnection dbc = availableConnections.iterator().next();
         availableConnections.remove(dbc);
         return dbc;

     }else {
         throw new NoDbConnections();
     }
   }

   public static void returnDbConnection(DbConnection dbc){
     availableConnections.add(dbc);
     //...
   }
}

如果我理解正确,您能否在方法returnDbConnection(DbConnection db)中添加availableConnections.add(db)? - Haifeng Zhang
@haifzhan,它并不是真正意图完整的,但可以。 - Matthew Flaschen
@MatthewFlaschen 还有,当连接被返回时,totalConnections是否应该减少? - rolling stone
@Sridhar,不是正在流通的数量,而是存在的连接数量(跟踪以防止创建超过MAX_CONNS的连接数)。 - Matthew Flaschen
@MatthewFlaschen 如果DbConnection是可关闭的,你的方法将被SonarCube识别为阻塞缺陷。 - Awan Biru
静态可变状态是反模式。应该使用实例和依赖注入。 - dQw4w9WyXcQ

75

通过使用静态工厂方法可以提高可读性:

对比:

public class Foo{
  public Foo(boolean withBar){
    //...
  }
}

//...

// What exactly does this mean?
Foo foo = new Foo(true);
// You have to lookup the documentation to be sure.
// Even if you remember that the boolean has something to do with a Bar
// you might not remember whether it specified withBar or withoutBar.
TO
public class Foo{
  public static Foo createWithBar(){
    //...
  }

  public static Foo createWithoutBar(){
    //...
  }
}

// ...

// This is much easier to read!
Foo foo = Foo.createWithBar();

所以我尝试实现你的例子,但是我不确定这是如何工作的。createWithBar和createWithoutBar两个方法是否应该调用Foo类内部的2个私有构造函数? - Essej
@Baxtex:是的。每个都会调用一个私有构造函数。也许只是相同的一个: private Foo(boolean withBar){/*..*/} public static Foo createWithBar(){return new Foo(true);} public static Foo createWithoutBar(){return new Foo(false);} - Rasmus Faber
1
我认为这个方法不太"通用"。如果你有三个或更多的参数,你该如何运用这个想法,并为这个方法创建一个好的名称? - Dherik
1
@Dherik:那么你应该使用建造者模式:new FooBuilder().withBar().withoutFoo().withBaz().build(); - Rasmus Faber

22
  • 和构造方法不同,具有名称,可以使代码更加清晰明了。
  • 无需在每次调用时创建新对象 - 如果需要,可以缓存并重复使用对象。
  • 可以返回其返回类型的子类型 - 特别地,可以返回一个其实现类对调用者未知的对象。这是许多框架中广泛使用的非常有价值的特性,这些框架将接口作为静态工厂方法的返回类型。

来自http://www.javapractices.com/topic/TopicAction.do?Id=21


20

所有问题归结为可维护性。最好的方式是,每当使用new关键字创建对象时,就将编写的代码与实现耦合在一起。

工厂模式让您将对象的创建与对其执行操作分离开来。当您使用构造函数创建所有对象时,实际上是将使用对象的代码与该实现硬连接在一起。使用您的对象的代码"依赖于"该对象。表面上可能看起来不是很重要,但是当对象发生更改(比如更改构造函数的签名或子类化对象)时,必须返回并在各个地方重新连接它们。

今天,工厂在很大程度上已被放弃,转而使用依赖注入,因为它们需要大量的样板代码,这些样板代码很难自行维护。依赖注入基本上相当于工厂,但允许您通过配置或注释声明指定对象如何组合在一起。


17
如果一个类的构造函数是私有的,那么你就不能从它的外部创建该类的对象。
class Test{
 int x, y;
 private Test(){
  .......
  .......
  }
}

我们无法在类的外部创建该类的对象。因此,您无法从类的外部访问x、y。那么,这个类有什么用呢?答案是:工厂方法。请在上述类中添加以下方法。
public static Test getObject(){
  return new Test();
}

现在,您可以从类的外部创建此类的对象。就像这样...

Test t = Test.getObject();

因此,通过执行其私有构造函数返回类对象的静态方法称为工厂方法。

嗨@Santhosh,我有一个疑问,为什么你不让私有构造函数变公共呢?如果你不想创建子类,那对我来说也可以,但如果我不想使用私有构造函数,Static Factory Method相较于公共构造函数有何优势呢? - Shen
我认为它看起来像这样: public static final Test Instance = new Test(); 您可以通过以下方式获取对象:Test.Instance.getObject(); - SangLe

10

我想为这篇文章增加一些关于我所知道的内容。我们在最近的安卓项目中广泛使用了这种技术。与其使用 "new" 运算符创建对象,你也可以使用静态方法来实例化一个类。代码清单如下:

我认为将这个技巧分享给更多人是非常有益的。

//instantiating a class using constructor
Vinoth vin = new Vinoth(); 

//instantiating the class using static method
Class Vinoth{
  private Vinoth(){
  }
  // factory method to instantiate the class
  public static Vinoth getInstance(){
    if(someCondition)
        return new Vinoth();
  }
}

静态方法支持条件对象创建:每次调用构造函数都会创建一个对象,但你可能并不想要这样。假设你只想在满足某些条件时才创建新对象。除非条件得到满足,否则你并不会每次创建一个新的Vinoth实例。

另一个例子来自于Effective Java

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
}

这种方法将布尔值转换为布尔对象引用。 Boolean.valueOf(boolean) 方法告诉我们,它从不创建对象。静态工厂方法 的能力可以从重复调用中返回同一对象,使得类可以严格控制任何时候存在的实例。

静态工厂方法 不同于 构造函数 的是,它们可以返回其返回类型的任何子类型的 对象。这种灵活性的一个应用是API可以返回对象而不使它们的类公开。以这种方式隐藏实现类会导致非常紧凑的API。

Calendar.getInstance() 是上述情况的一个很好的例子,它根据语言环境创建一个 BuddhistCalendarJapaneseImperialCalendar 或默认的一个 Georgian

另一个我想到的例子是Singleton模式,其中您使构造函数私有化并创建一个自己的 getInstance 方法,在该方法中确保始终只有一个实例可用。

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

4

静态工厂的一个优点是API可以返回对象而不需要使它们的类公开。这导致了非常紧凑的API。在Java中,这是通过Collections类实现的,该类隐藏了大约32个类,从而使集合API非常紧凑。


4

工厂方法是一种抽象化对象实例化的方法。通常情况下,当您需要一个实现某个接口的类的新实例,但不知道具体实现类时,工厂就非常有用。

在处理相关类层次结构时,工厂方法非常有用,例如GUI工具包。如果您只是简单地硬编码调用每个小部件的具体实现构造函数,但如果您想要将一个工具包替换为另一个,则需要修改很多代码。通过使用工厂,您可以减少需要更改的代码量。


假设你的工厂返回一个接口类型,而不是你正在处理的具体类。 - Bill Lynch
1
这个答案是关于工厂方法设计模式,而不是静态工厂方法。静态工厂方法只是一个公共的静态方法,返回一个类的实例。请参阅《Effective Java》第2章获取更多细节。 - Josh Sunshine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接