就我个人而言,我从来没有理解工厂类的概念,因为直接实例化一个 Object 似乎更有用。我的问题很简单,在什么情况下使用工厂类模式是最佳选择,出于什么原因,一个好的工厂类是什么样的?
7 回答
这是我的代码库中的一个真实的工厂。它用于生成一个知道如何从某个数据集中采样数据的采样器类(它最初是在 C# 中,所以请原谅任何 java 错误)
class SamplerFactory
{
private static Hashtable<SamplingType, ISampler> samplers;
static
{
samplers = new Hashtable<SamplingType, ISampler>();
samplers.put(SamplingType.Scalar, new ScalarSampler());
samplers.put(SamplingType.Vector, new VectorSampler());
samplers.put(SamplingType.Array, new ArraySampler());
}
public static ISampler GetSampler(SamplingType samplingType)
{
if (!samplers.containsKey(samplingType))
throw new IllegalArgumentException("Invalid sampling type or sampler not initialized");
return samplers.get(samplingType);
}
}
这是一个示例用法:
ISampler sampler = SamplerFactory.GetSampler(SamplingType.Array);
dataSet = sampler.Sample(dataSet);
如您所见,它的代码并不多,甚至可能会更短更快
ArraySampler sampler = new ArraySampler();
dataSet = sampler.Sample(dataSet);
而不是使用工厂。那我为什么还要打扰呢?嗯,有两个基本原因,它们相互依赖:
首先是代码的简单性和可维护性。假设在调用代码中,
enum
是作为参数提供的。即,如果我有一个需要处理数据的方法,包括采样,我可以写:void ProcessData(Object dataSet, SamplingType sampling) { //do something with data ISampler sampler = SamplerFactory.GetSampler(sampling); dataSet= sampler.Sample(dataSet); //do something other with data }
而不是更繁琐的构造,如下所示:
void ProcessData(Object dataSet, SamplingType sampling) { //do something with data ISampler sampler; switch (sampling) { case SamplingType.Scalar: sampler= new ScalarSampler(); break; case SamplingType.Vector: sampler= new VectorSampler(); break; case SamplingType.Array: sampler= new ArraySampler(); break; default: throw new IllegalArgumentException("Invalid sampling type"); } dataSet= sampler.Sample(dataSet); //do something other with data }
请注意,每次我需要采样时都应该编写这个怪物。你可以想象,如果我在
ScalarSampler
构造函数中添加了一个参数,或者添加了一个新的SamplingType
. 而这个工厂现在只有三个选项,想象一个有 20 个实现的工厂。其次,是代码的解耦。当我使用工厂时,调用代码不知道或不需要知道被调用的类
ArraySampler
甚至存在。该类甚至可以在运行时解析,调用站点也不会更明智。因此,因此,我可以随意更改ArraySampler
类,包括但不限于彻底删除类,例如,如果我决定也ScalarSampler
应该将 用于数组数据。我只需要换行samplers.put(SamplingType.Array, new ArraySampler());
到
samplers.put(SamplingType.Array, new ScalarSampler());
它会神奇地工作。我不必更改调用类中的一行代码,这可能有数百个。实际上,工厂使我能够控制采样发生的内容和方式,并且任何采样更改都有效地封装在与系统其余部分接口的单个工厂类中。
这里的想法是关注点分离:如果使用对象的代码也有足够的信息来实例化它,则不需要工厂。但是,如果涉及某些您不希望 API 用户考虑(或弄乱)的逻辑或配置,您可以在工厂中隐藏所有这些(并将其封装以供重用)。
下面是一个示例:您想要访问 Google App Engine 提供的一项服务。相同的代码应该在开发环境(有两个版本,主从和高可用)和完全不同的本地开发环境下都可以工作。谷歌不想告诉你他们内部基础设施的内部运作,你也不想知道。所以他们所做的是提供接口和工厂(以及这些接口的几个实现供工厂选择,您甚至不需要知道)。
就个人而言,当接口的实现在运行时未知或者可以动态化时,我会使用工厂模式。
这意味着作为开发人员,我使用对象实例的已知接口,但我不关心实现的工作方式。
举个例子。您可以使用工厂模式为您提供数据库中的对象。您不关心该数据库是平面文件、本地/单用户数据库、服务器数据库还是 Web 资源,只要工厂可以生成和管理这些对象。
我不想为每种情况编写实现:P
来自 Joshua Bloch 的 Effective Java 书,由我部分重写:
1) 与构造函数不同,静态工厂方法 ( SFM ) 有名称。
public static ComplexNumber one () {
return new ComplexNumber(1, 0);
}
public static ComplexNumber imgOne () {
return new ComplexNumber(0, 1);
}
public static ComplexNumber zero () {
return new ComplexNumber(0, 0);
}
2) 不需要每次SFM
调用都创建一个新对象
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
3)SFM
可以返回其返回类型的任何子类型的对象。
4)SFM
减少创建参数化类型实例的冗长。
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
Map<String, List<String>> m = HashMap.newInstance();
为了补充 Thilo 的答案,让我们假设您有一个对象,它只有一个布尔值作为构造函数:每次都构建一个完全是浪费,因为它只有两个可能的值。
在这种情况下,您可以创建静态工厂方法。Java 的Boolean
类就是一个例子:Boolean.valueOf()
.
您可以参考维基百科,但大多数设计模式的基本思想是引入一些抽象以实现更好的可维护性和/或可重用性。工厂方法模式也不例外,它所做的就是从代码中抽象出创建的复杂性。
对于简单的情况,似乎没有必要使用工厂模式,一个简单new
的就足够了。但是当您需要更多的灵活性或功能时,这种模式可能会有所帮助。
例如,除非需要新实例,否则静态工厂valueOf(boolean)
通常是比 更好的选择new Bealean(boolean)
,因为它避免了创建不必要的对象。工厂方法模式也称为Virtual Constructor。众所周知,多态是OOP的关键特性之一,但构造函数不能是多态的,这个缺点可以通过工厂方法模式来克服。
本质上,直接实例化一个对象(通常是通过new
)几乎不是一个具体的实现,而工厂方法模式通过一个稳定的接口(不限于 Java 中)屏蔽了一个volatile的 实现,将创建对象的逻辑推到了一些抽象后面,以确保更具可维护性和可重用性的代码。 interface
工厂本身并没有那么容易展示它的美丽。当您将它与其他模式结合时,您会看到真正的好处,例如,如果您想使用装饰器模式,直接实例化一个对象可能会为您的代码添加一些额外的耦合。正如 OOP 老师所说,耦合是不好的 :) 所以如果你要实例化装饰对象并且不想增加耦合,那么你可以使用工厂。