公共静态工厂方法

Posted

技术标签:

【中文标题】公共静态工厂方法【英文标题】:public static factory method 【发布时间】:2011-05-04 00:40:33 【问题描述】:

首先,如果这是一个非常愚蠢的问题,请原谅我,我只是在努力学习这门语言的核心。我正在阅读 Effective Java,第一章讨论的是静态工厂方法与构造函数。他们的优点和缺点。让我感到困惑的几件事是:

    静态工厂方法返回的对象的类是非公开的 - 究竟是什么意思? 与构造函数不同,静态工厂方法不需要在每次调用时都创建一个新对象 - 这是怎么发生的?我调用工厂方法只是为了获取一个新对象,我们是否在工厂方法中检查对象是否已经存在?

谢谢。

【问题讨论】:

+1 用于深入了解软件设计实践并提出一个好问题。 +1 用于询问 WHY 而不是仅仅通过 write 进行编程。优秀程序员的习惯。 知道在哪里可以找到一些real world examples 并检查他们的源代码可能有助于更好地理解设计模式。 【参考方案1】:

您的两个问题都可以通过查看一些利用静态工厂方法的这两个属性的代码来回答。我建议看看 Guava 的ImmutableList。

注意无参数工厂方法of() 总是返回相同的实例(它不会每次都创建一个新实例)。如果你仔细看,你还会注意到它的copyOf(Iterable) 工厂方法实际上返回了传递给它的对象,如果该对象本身是一个ImmutableList。这两者都利用了ImmutableList 保证永远不会改变的事实。

还要注意其中的各种工厂方法如何返回不同的子类,例如EmptyImmutableListSingletonImmutableListRegularImmutableList,而不暴露这些对象的类型。方法签名只是显示它们返回ImmutableList,并且ImmutableList 的所有子类都具有包私有(默认)可见性,使它们对库用户不可见。从用户的角度来看,这提供了多个实现类的所有优点,而不会增加任何复杂性,因为它们只允许将 ImmutableList 视为单一类型。

除了ImmutableList,Guava 中的大多数可实例化类都使用静态工厂方法。 Guava 还体现了 Effective Java 中提出的许多原则(这并不奇怪,因为它是根据这些原则设计的,并得到了 Josh Bloch 本人的指导),因此您可能会发现更多地了解它很有用。正在阅读这本书。

【讨论】:

感谢科林的例子。经历它。【参考方案2】:

首先,感谢您选择 Java-lit:Bloch 的书是一本优秀的入门书。

要回答您的第二个问题('与构造函数不同,静态工厂方法不需要在每次调用它们时都创建一个新对象'),重要的是要意识到 Bloch 在这里所说的是,使用静态工厂,您拥有选项:返回一个新对象或返回一个预先存在的对象。这一切都取决于你想做什么。

例如,假设您有一个非常简单的 Money 类型的值类。您的静态工厂方法可能应该返回一个新实例——即具有特定 Money 值的新对象。所以,像这样:

public class Money  

    private Money(String amount)  ...  /* Note the 'private'-constructor */

    public static Money newInstance(String amount) 
        return new Money(amount);
    


但是,假设您有一些管理某个资源的对象,并且您希望通过某个 ResourceManager 类同步对该资源的访问。在这种情况下,您可能希望您的静态工厂方法将自身的相同实例返回给每个人——强制每个人都经过同一个实例,以便该 1 个实例可以控制该过程。这遵循单例模式。像这样的:

public ResourceManager 

    private final static ResourceManager me = new ResourceManager();

    private ResourceManager()  ...  /* Note the 'private'-constructor */

    public static ResourceManager getSingleton() 
        return ResourceManager.me;
    

上述方法强制您的用户只能使用单个实例,从而允许您精确控制谁(以及何时)可以访问您正在管理的任何内容。


要回答您的第一个问题,请考虑以下问题(诚然,这不是最好的例子,它非常临时):

public class Money 

    private Money(String amount)  ... 


    public static Money getLocalizedMoney( MoneyType localizedMoneyType, String amount )  
        switch( localizedMoneyType ) 
            case MoneyType.US:
                return new Money_US( amount );
            case MoneyType.BR:
                return new Money_BR( amount );
            default:
                return new Money_US( amount );
        
    


public class Money_US extends Money  ... 

public class Money_BR extends Money  ... 

注意我现在如何做到这一点:

Money money = Money.getLocalizedMoney( user_selected_money_type );
saveLocalizedMoney( money );

再次,一个非常人为的示例,但希望它可以帮助您或多或少地了解 Bloch 在这一点上的意思。

其他答案很好——我只是认为,作为初学者,有时查看一些实际代码会有所帮助。

【讨论】:

> 只是认为,作为初学者,有时查看一些实际代码会有所帮助。感谢您考虑这个事实祸根。您的示例非常有帮助,尤其是您创建私有实例并每次为 ResourceManager 返回相同实例的示例。 np -- 每当学习一个新概念时,我总是与抽象/模糊的答案作斗争 -- 硬代码加上简短的解释通常对我来说更进一步。顺便说一句,Bloch 书中的“第 3 项”将为您提供更多关于单例工厂方法的内容。 @Shen 答案太长,无法在此回答;作者自己列出了 5 个理由,大约需要 4 整页来解释他的推理。请参阅 Joshua Bloch 的“Effective Java”,“第 1 项:考虑静态工厂方法而不是构造函数”。然而,简短的回答是,您并不严格“必须这样做”,而是使用静态工厂方法为您提供了更多的灵活性。现在,特别提到您关于“第一个示例”的问题,该示例有点做作和简单化;不一定最清楚地传达为什么 @Shen 说,它仍然可以作为未来改进的基础。如果您只是使用构造函数编写,那么稍后当您想引入我在第一个示例之后显示的“本地化货币类型”代码时,您可能会被锁定在该模式中。另一方面,如果您默认隐藏构造函数,那么您的简单工厂模式可用于调用getLocalizedMoneyType(DefaultMoneyTypeBasedOnLocale, amount)——并且没有预先存在的客户端代码中断,因为没有人已经在做new Money(amount) @Shen 正确。对于最简单的值类,这可能是矫枉过正。但是对于您设想的任何事情都可能会变得更复杂(例如,想想其他人以 API 方式使用此代码;请注意,不一定是 REST-API;只是通用 API)然后是的,这就是我要说的:我们应该使用静态工厂方法来抽象出最终用户未来对底层 impl 的更改,并为我们的开发人员提供更灵活的设计。【参考方案3】:

静态工厂方法返回的对象的类是非公开的

静态工厂方法通常会返回一个类型为接口的对象(最常见),或者有时会返回一些基类(不太常见)。无论哪种情况,您都不知道返回对象的确切类。

这样做的好处是获得一个你知道其行为的对象,而不必担心它实例化的类的混乱细节。

与构造函数不同,静态工厂方法不需要在每次调用时都创建一个新对象

要理解这一点,请考虑使用单例的情况。您可以在某些工厂类上调用 .getInstance() 来获取某个对象的单例实例。通常,如果它不存在,它会创建一个对象的实例,或者如果它已经存在,则为您提供现有实例。在任何一种情况下,您都会取回对象的副本。但是你不(也不会)知道这个单例是否必须被创建,或者之前是否已经构建过。

这样做的好处是可以为您管理对象的生命周期和创建时间。

【讨论】:

嗨,Trey,您的单例示例确实消除了我的疑问。谢谢:)【参考方案4】:

当您使用 new 关键字时,作为开发人员的您知道 JDK 将创建该对象的新实例。作者的意思是,当您使用静态方法时,开发人员不再知道该方法是在创建新实例还是可能在做其他事情。其他方法可以是重用缓存数据、对象池、创建私有实现并返回该类的子类。

【讨论】:

您可能并不总是需要某个东西的新实例。以数据库连接为例:您在 java jdbc.newConnection() 中进行。但是java不会每次都创建一个新实例。它首先检查连接是否已经存在。如果没有,它将获得一个新实例。另一个例子可能是你想创建一个单例。 (单身人士有自己的问题)这意味着有一个很好的阅读为什么应该只有一次类的实例。因此,您再次将构造函数设为私有,并且只允许通过静态类使用您的 api。 @Amir:与开发人员必须寻找的一些令人困惑的方法相比,使用构造函数必须更简洁...一致的命名约定可以解决这个问题。浏览静态方法名称比尝试根据参数确定要使用的 100 个构造函数中的哪一个要容易得多。构造函数很容易出错(“哦,我想要new SomeType( int, int, String, int),而不是new SomeType(int, int, int, String)...”) @Amir:这个缺点就在那里,所以争论看起来很平衡。使用注解和适当的命名可以轻松地为您提供所需的静态支持,以便在 Javadocs 等中区分它们。根本不是不使用它们的好理由。 @Amir:您无法在 Java 中选择构造函数名称。当然,我不会为每一堂课都这样做,但肯定不是因为不够清晰。还有其他正当理由(例如,如果要扩展类型,基本上是不可能的)。 @Amir:我和 Mark 一起讨论这个问题——非常坚定。静态工厂方法是的方法。话虽这么说,如果您正在编写一个简单的值类,打算由您和您一个人使用,那么(我的经验法则)我不担心静态工厂类——我只使用构造函数。但是 Bloch 书中的大部分内容都应该在 API DEVELOPER 的上下文中理解。也就是说,如果您正在开发一个供其他人使用的 API,是的,您几乎应该始终使用静态工厂方法。【参考方案5】:

静态工厂方法返回的对象的类是非公共的 - 究竟是什么意思?

这意味着静态工厂方法返回的对象的实际类可以是声明类型的子类,并且该子类不必是公共的。这只是客户端代码不应该关心的另一个实现细节。

与构造函数不同,静态工厂方法不需要在每次被调用时都创建一个新对象——这是怎么发生的?我调用工厂方法只是为了获取一个新对象,我们是否在工厂方法中检查对象是否已经存在?

是的,这是可以做到的一种方式。但实际上,一切皆有可能。

【讨论】:

嗨迈克尔,所以这取决于要求?没有硬性规定工厂方法应始终检查已存在的实例。 @t3ch:是的,当然。关键是您可以使用工厂方法来做到这一点,如果它有用并且你想要做什么......你没有new的选项。 哇,谢谢。至少现在我知道它在哪里有用。使用这种方法可以更好地理解单例。

以上是关于公共静态工厂方法的主要内容,如果未能解决你的问题,请参考以下文章

简单工厂模式

java之简单工厂

静态工厂的第四个优点是返回对象的类可以根据输入参数的不同而不同。

设计模式01——静态工厂工厂方法抽象工厂

转发Java设计当中的工厂设计模式

设计模式之简单工厂模式