赞
踩
工厂是每个开发人员都应该知道的关键创造模式之一。它们是许多高级模式的主要组成部分。很长一段时间,我在不同类型的工厂模式上遇到了麻烦。此外,在同一篇文章中很难找到有关这些类型的信息。本文介绍 4 种类型的工厂模式:
工厂方法模式在“设计模式:可重用面向对象软件的元素”*一书中由四人帮描述。我第一次读到这个模式时,我用静态模式误解了它,这是Joshua Bloch(Java API的主要架构师之一)在他的书“Effective Java”中描述的。简单的工厂(有时称为工厂)是非正式的,但在网上出现了很多次。最后一个,抽象的工厂模式,也在“四人帮”的书中进行了描述,并且是工厂方法模式的更广泛概念。
在这篇文章中,我将解释为什么工厂是有用的,然后我将用来自著名Java框架或Java API的真实示例来介绍每种类型。我将使用Java代码来实现工厂,但如果你不了解Java,你仍然能够理解这个想法。此外,我将使用 UML 来正式描述模式。
虽然本文是关于工厂模式的,但仅仅为了使用模式而使用模式比从不使用模式更糟糕。这种行为是一种反模式。实际上,大多数模式使代码更难以理解。大多数时候,我不使用工厂。例如:
我一直认为工厂是它们的优势(将在下一部分中看到它们)与代码的可读性和理解性之间的权衡。
工厂的主要目标是实例化对象。但是为什么不直接使用构造函数调用创建 objets 呢?
对于简单的用例,无需使用工厂。让我们看一下这段代码。
public class SimpleClass { private final Integer arg1; private final Integer arg2; SimpleClass(Integer arg1, Integer arg2) { this.arg1 = arg1; this.arg2 = arg2; } public Integer getArg1(){ return arg1; } public Integer getArg2(){ return args; } } ... public class BusinessClassXYZ { public static void someFunction(){ SimpleClass mySimpleClass = new SimpleClass(1,2); // some stuff } }
在此代码中,SimpleClass 是一个非常简单的类,具有状态、无依赖项、无多态性和业务逻辑。您可以使用工厂来创建此对象,但它会使代码量增加一倍。因此,这将使代码更难以理解。**如果你能避免使用工厂来做到这一点,**你最终会得到一个更简单的代码!
但是,在编写需要许多开发人员和许多代码更改的大型应用程序时,您经常会遇到更复杂的情况。对于这些复杂的案例,工厂的优势超过了它们的缺点。
现在我警告过你关于工厂的使用,让我们看看为什么它们如此强大,因此在大多数项目中使用。
企业应用程序的一个常见用例是限制类的实例数。您如何设法只拥有一个(或2个,或10个)类的实例,因为它消耗的资源,如套接字,数据库连接,或文件系统描述符或其他任何东西?
使用构造函数方法,不同的函数(来自不同的类)很难知道类的实例是否已存在。而且,即使有一个实例,函数如何获取此实例?您可以通过使用共享变量来做到这一点,每个函数都会检查这些变量,但是
使用静态工厂方法,您可以轻松地做到这一点:
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } ... } ... public class ClassXXX{ ... public static void someFunctionInClassXXX(){ Singleton instance = Singleton.getInstance(); //some stuff } } ... public class ClassYYY{ ... public static void someFunctionInClassYYY(){ Singleton instance = Singleton.getInstance(); //some stuff } }
在此代码中,我们使用的工厂将类 Singleton 的实例数限制为 1。通过限制对象的数量,我们正在创建一个实例池,而这个池模式基于工厂。
注意:我们可以修改实例的创建方式(例如,通过使用原型模式而不是每次都从头开始创建新对象),而不是限制实例的数量。
工厂的另一个优点是松散耦合。
假设您编写了一个计算内容并需要写入日志的程序。由于这是一个很大的项目,你的一个伙伴编码,当你对业务类进行编码时,将日志写入文件系统的类(类FileSystemLogger)。如果没有工厂,您需要在使用构造函数之前使用构造函数实例化 FileSystemLogger:
public class FileSystemLogger {
...
public void writeLog(String s) {
//Implemation
}
}
...
public void someFunctionInClassXXX(some parameters){
FileSystemLogger logger= new FileSystemLogger(some paramters);
logger.writeLog("This is a log");
}
但是,如果突然发生更改,并且您现在需要使用 implememmentation DatabaseLogger 在数据库中写入日志,会发生什么情况?如果没有工厂,则必须使用 FileSystemLogger 类修改所有函数。由于此记录器在任何地方都使用,因此您需要修改数百个函数/类,而使用工厂,您可以通过仅修改工厂来轻松地从一个实现切换到另一个实现:
//this is an abstraction of a Logger public interface ILogger { public void writeLog(String s); } public class FileSystemLogger implements ILogger { ... public void writeLog(String s) { //Implemation } } public class DatabaseLogger implements ILogger { ... public void writeLog(String s) { //Implemation } } public class FactoryLogger { public static ILogger createLogger() { //you can choose the logger you want // as long as it's an ILogger return new FileSystemLogger(); } } some code using the factory public class SomeClass { public void someFunction() { //if the logger implementation changes //you have nothing to change in this code ILogger logger = FactoryLogger.createLogger(); logger.writeLog("This is a log"); } }
如果您查看此代码,则可以轻松地将记录器实现从 FileSystemLogger 更改为 DatabaseLogger。你只需要修改函数 createLogger() (这是一个工厂)。此更改对于客户端(业务)代码是不可见的,因为客户端代码使用记录器(ILogger)的接口,并且记录器实现的选择由工厂进行。通过这样做,您可以在记录器的实现和使用记录器的代码部分之间创建松散耦合。
有时,使用工厂可以提高代码的可读性,并通过封装降低其复杂性。
假设您需要使用比较2辆车的商务级汽车比较器。这个类需要一个 DatabaseConnection 来获取数百万辆汽车的功能,需要一个 FileSystemConnection 来获取一个配置文件来参数化比较算法(例如:在油耗上增加比最大速度更多的汽油)。
如果没有工厂,你可以编写如下代码:
public class DatabaseConnection { DatabaseConnection(some parameters) { // some stuff } ... } public class FileSystemConnection { FileSystemConnection(some parameters) { // some stuff } ... } public class CarComparator { CarComparator(DatabaseConnection dbConn, FileSystemConnection fsConn) { // some stuff } public int compare(String car1, String car2) { // some stuff with objets dbConn and fsConn } } ... public class CarBusinessXY { public void someFunctionInTheCodeThatNeedsToCompareCars() { DatabaseConnection db = new DatabaseConnection(some parameters); FileSystemConnection fs = new FileSystemConnection(some parameters); CarComparator carComparator = new CarComparator(db, fs); carComparator.compare("Ford Mustang","Ferrari F40"); } ... } public class CarBusinessZY { public void someOtherFunctionInTheCodeThatNeedsToCompareCars() { DatabaseConnection db = new DatabaseConnection(some parameters); FileSystemConnection fs = new FileSystemConnection(some parameters); CarComparator carComparator = new CarComparator(db, fs); carComparator.compare("chevrolet camaro 2015","lamborghini diablo"); } ... }
此代码有效,但您可以看到,为了使用比较方法,您需要实例化
如果您需要在多个函数中使用比较,则必须复制代码,这意味着如果 CarComparator 的构造发生变化,则必须修改所有重复的部分。工厂的使用可能会使代码分解,并隐藏CarComparator类构造的复杂性。
... public class Factory { public static CarComparator getCarComparator() { DatabaseConnection db = new DatabaseConnection(some parameters); FileSystemConnection fs = new FileSystemConnection(some parameters); CarComparator carComparator = new CarComparator(db, fs); } } //some code using the factory public class CarBusinessXY { public void someFunctionInTheCodeThatNeedsToCompareCars() { CarComparator carComparator = Factory.getCarComparator(); carComparator.compare("Ford Mustang","Ferrari F40"); } ... } ... public class CarBusinessZY { public void someOtherFunctionInTheCodeThatNeedsToCompareCars() { CarComparator carComparator = Factory.getCarComparator(); carComparator.compare("chevrolet camaro 2015","lamborghini diablo"); } ... }
如果比较这两个代码,您可以看到使用工厂:
最后一点很重要(事实上,它们都很重要!),因为这是关于关注点的分离。业务类不必知道如何构建它需要使用的复杂对象:业务类只需要关注业务问题。此外,它还增加了同一项目开发人员之间的工作分工:
假设您有一个具有多个构造函数(具有非常不同的行为)的类。您如何确保不会错误地使用错误的构造函数?
让我们看一下下面的代码:
class Example{
//constructor one
public Example(double a, float b) {
//...
}
//constructor two
public Example(double a) {
//...
}
//constructor three
public Example(float a, double b) {
//...
}
}
虽然构造函数 1 和 2 的参数数量不同,但您很快就无法选择正确的参数,尤其是在忙碌的一天结束时,使用您喜欢的 IDE 中漂亮的自动完成功能(我去过那里)。更难看出构造函数 1 和构造函数 3 之间的区别。这个例子看起来像一个假的,但我在遗留代码上看到了它(真实的故事!
问题是,您如何实现具有相同类型参数的不同构造函数(同时避免像构造函数一和三这样的脏方式)?
以下是使用工厂的干净解决方案:
class Complex {
public static Complex fromCartesian(double real, double imag) {
return new Complex(real, imag);
}
public static Complex fromPolar(double rho, double theta) {
return new Complex(rho * Math.cos(theta), rho * Math.sin(theta));
}
private Complex(double a, double b) {
//...
}
}
在此示例中,使用工厂添加工厂方法名称对创建内容的描述:您可以从笛卡尔坐标或极坐标创建复数。在这两种情况下,您都确切地知道创作是关于什么的。
现在我们已经看到了工厂的优缺点,让我们关注不同类型的工厂模式。
我将从最简单到最抽象地介绍每个工厂。如果你想使用工厂,请记住,越简单越好。
注意:如果您阅读了本文,并且对Java了解不多,那么静态方法就是类方法。
静态工厂方法由Joshua Bloch在“Effective Java”中描述:
“类可以提供公共静态工厂方法,该方法只是返回类实例的静态方法。
换句话说,类可以提供返回实例的静态方法,而不是使用构造函数来创建实例。如果此类具有子类型,则静态工厂方法可以返回该类的类型或其子类型。虽然我讨厌UML,但我在文章的开头说过。在这里:
在此图中,类 ObjectWithStaticFactory 有一个静态工厂方法(称为 getObject())。此方法可以实例化任何类型的类 ObjectWithStaticFactory,这意味着类型 ObjectWithStaticFactory 或类型 SubType1 或类型 SubType2。当然,此类可以具有其他方法、属性和静态工厂方法。
让我们看一下这段代码:
public class MyClass { Integer a; Integer b; MyClass(int a, int b){ this.a=a; this.b=b; }; public static MyClass getInstance(int a, int b){ return new MyClass(a, b); } public static void main(String[] args){ //instanciation with a constructor MyClass a = new MyClass(1, 2); //instanciation with a static factory method MyClass b = MyClass.getInstance(1, 2); } }
此代码显示了创建 MyClass 实例的 2 种方法:
但这个概念可以更深入。如果具有静态工厂方法的类可以实例化另一个类,该怎么办?Joshua Bloch描述了这种可能性:
“接口不能有静态方法,因此按照惯例,名为 Type 的接口的静态工厂方法被放在名为 Types 的不可检验类(Item 4)中。
在本例中,工厂方法 getObject 位于抽象类名 Types 中。工厂方法可以创建类 Type 的实例或类 Type 的任何子类型(图中的 SubType1 或 SubType2)。getObject() 方法可以具有参数,以便它返回给定参数的 SubType1,否则返回 SubType2。
让我们回到Java中,假设我们有2个类:法拉利和野马,它们实现了一个接口汽车。静态工厂方法可以放在一个名为“CarFactory”的抽象类中(使用Joshua Boch的约定,类的名称应该是“Cars”,但我不喜欢它):
/the products public interface Car { public void drive(); } public class Mustang implements Car{ public void drive() { // some stuff } ... } public class Ferrari implements Car{ public void drive() { // some stuff } ... } / the factory public abstract class CarFactory{ public static Car getCar() { // choose which car you want } } ... /some code using the factory public static void someFunctionInTheCode(){ Car myCar = CarFactory.getCar(); myCar.drive(); }
与其他工厂模式相比,此模式的强大之处在于您不需要
它易于使用,但仅适用于提供类方法(即静态java关键字)的语言。
注意:当涉及到工厂时,网络上的许多帖子都是错误的,就像这个关于stackoverflow的帖子一样,它被提高了1.5k倍。给定的工厂方法模式示例的问题在于它们是静态工厂方法。如果我引用Joshua Bloch的话:
“静态工厂方法与设计模式中的工厂方法模式不同 [Gamma95, p. 107]。本项中描述的静态工厂方法在
设计模式中没有直接的等效项。
如果你看一下stackoverflow的文章,只有最后一个例子(URLStreamHandlerFactory)是GoF的工厂方法模式(我们将在几分钟内看到这个模式)
下面是 Java 框架和 Java API 中静态工厂方法的一些示例。在Java API中查找示例非常容易,因为Joshua Bloch是许多Java API的主要架构师。
java logging 框架 slf4j、logback 和 log4j 使用抽象类 LoggerFactory。如果开发人员想要写入日志,他需要从 LoggerFactory 的静态方法 getLogger() 中获取 Logger 的实例。
getLogger() 返回的 Logger 实现将取决于 getLogger() 的实现(以及 getLogger() 使用的开发人员编写的配置文件)。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Example{
public void example() {
//we're using the static factory method to get our logger
Logger logger = LoggerFactory.getLogger(Example.class);
logger.info("This is an example.");
}
}
注意:工厂类的名称及其静态工厂方法的名称并不完全相同,无论您使用的是 slf4j 还是 log4j 还是 slf4j。
Java 中的 String 类表示一个字符串。有时,您需要从布尔值或整数中获取字符串。但是 String 不提供 String(Integer i) 或 String(Boolean b) 这样的构造函数。相反,它提供了多个静态工厂方法 String.valueOf(…)。
int i = 12;
String integerAsString = String.valueOf(i);
这种模式不是“真实的”,但我在互联网上已经多次看到它。它没有正式的描述,但这是我的:一个简单的工厂(或工厂)是一个工具。
你可以看到它具有静态工厂模式的泛化,但这次工厂可以被实例化(或不实例化),因为“工厂方法”不是类方法(但它可以)。对于Java开发人员来说,以非静态形式使用简单工厂是罕见的**。**因此,此模式在大多数情况下等效于静态模式。
在本例中,工厂方法 getObject() 位于名为 Factory 的类中。工厂方法不是类方法,因此,您需要在使用工厂之前对其进行实例化。工厂方法可以创建类 Type 或其任何子类型的实例。
下面是静态工厂方法的上一个示例,但这次我在使用之前先实例化了工厂
/the products public interface Car { public void drive(); } public class Mustang implements Car{ public void drive() { // some stuff } ... } public class Ferrari implements Car{ public void drive() { // some stuff } ... } /The factory public class CarFactory{ //this class is instantiable public CarFactory(){ //some stuff } public Car getCar() { // choose which car you want } } ... /some code using the factory public static void someFunctionInTheCode(){ CarFactory carFactory = new CarFactory(); Car myCar = carFactory.getCar(); myCar.drive(); }
如您所见,这次我需要实例化工厂才能使用它。我没有在java中找到真实的例子**,因为使用静态工厂方法比使用简单的工厂更好**。不过,如果您的工厂方法需要某些实例才能工作,则可以以非静态形式使用此模式。例如,如果需要数据库连接,可以先实例化工厂(这将实例化数据库连接),然后使用需要此连接的 factory 方法。就个人而言,在这种情况下,我仍然会使用具有延迟初始化的静态工厂(以及数据库连接池)。
告诉我,如果你知道一个Java框架,它以非静态的形式使用一个简单的工厂。
工厂方法模式是一个更抽象的工厂。以下是“四人帮”给出的模式定义:
“定义一个用于创建对象的接口,但让子类决定要实例化的类。工厂方法允许类将实例化推迟到子类”
下面是工厂方法模式的简化图:
此图看起来像简单的工厂图(以非静态形式)。唯一(和BIG!)的区别是接口 Factory:
在图中,getObjects() 必须返回 Type(或其子类型)。这意味着一个连接器工厂可以返回子类型1,而另一个工厂可以返回子类型2。
为什么使用工厂方法模式而不是简单的工厂?
仅当代码需要多个工厂实现时。这将强制每个工厂具有相同的逻辑,以便使用一个实现的开发人员可以轻松切换到另一个实现,而无需徘徊如何使用它(因为他只需要调用具有相同签名的工厂方法)。
由于这是抽象的,让我们回到汽车的例子。这不是一个很好的例子,但我使用它,以便你可以看到与简单工厂的区别,(我们将看到真实的例子来理解这个模式的力量):
/the products public interface Car { public void drive(); } public class Mustang implements Car{ public void drive() { // some stuff } ... } public class Ferrari implements Car{ public void drive() { // some stuff } ... } / the factory //the definition of the interface public interface CarFactory{ public Car getCar() {} } //the real factory with an implementation of the getCar() factory method public class ConcreteCarFactory implements CarFactory{ //this class is instantiable public CarFactory(){ //some stuff } public Car getCar() { // choose which car you want return new Ferrari(); } } ... /some code using the factory public static void someFunctionInTheCode(){ CarFactory carFactory = new ConcreteCarFactory(); Car myCar = carFactory.getCar(); myCar.drive(); }
如果你将这段代码与简单的工厂进行比较,我这次添加了一个接口*(CarFactory)。*真正的工厂(*ConcreteCarFactory)*实现了这个接口。
正如我所说,这不是一个很好的例子,因为在**此示例中,你不应该使用工厂方法模式,因为**只有一个具体的工厂。**只有当我有多个实现,如SportCarFactory,VintageCarFactory,LuxeCarFactory,CheapCarFactory…时,它才会有用。在这种情况下,开发人员可以很容易地从一个实现切换到另一个实现,因为工厂方法总是getCar()。
在java中,一个常见的例子是集合API中的iterator()函数。每个集合都实现接口 Iterable 。此接口描述一个返回迭代器的函数迭代器()。ArrayList 是一个集合。因此,它实现了接口 Iterable 及其返回迭代器<E 子类的工厂方法 iterator()>
//下面是java源代码中迭代器的简化定义 public interface Iterator<E> { boolean hasNext(); E next(); void remove(); } //工厂界面来了! public interface Iterable<T> { Iterator<T> iterator(); } //下面是java源代码中ArrayList的简化定义 //您可以看到,这个类是一个具体的工厂,它实现 //a factory method iterator() //注意:在真正的Java源代码中,ArrayList派生自 //AbstractList是实现工厂方法模式的抽象列表 public class ArrayList<E> { //the iterator() returns a subtype and an "anonymous" Iterator<E> public Iterator<E> iterator() { return new Iterator<E>() { //implementation of the methods hasNext(), next() and remove() } } ... }
这是 ArrayList 的标准用法
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Example {
public static void main(String[] ars){
//instantiation of the (concrete factory) ArrayList
List<Integer> myArrayList = new ArrayList<>();
//calling the factory method iterator() of ArrayList
Iterator<Integer> myIterator = myArrayList.iterator();
}
}
我展示了一个ArrayList,但我可以展示一个HashSet,一个LinkedList或一个HashMap,因为它们都是集合API的一部分。这种模式的优势在于,你不需要知道你正在使用什么类型的集合,每个集合都将通过工厂方法迭代器()提供一个迭代器。
另一个很好的例子是新的Java 8集合API中的stream()方法。
Spring框架基于工厂方法模式。ApplicationContext 实现了 BeanFactory Interface。此接口描述了一个返回 Object 的函数 Object getBean(param)。这个例子很有趣,因为java中的每个类都是从Object派生的。因此,此工厂可以返回任何类的实例(取决于参数)。
public class Example{
public static void main(String[] args) {
//creation of the BeanFactory
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
//creation totaly different type of objets with the factory
MyType1 objectType1 = context.getBean("myType1");
MyType2 objectType2 = context.getBean("myType2");
}
}
胖子来了!这个工厂被四人帮用下面这句话来描述:
“提供一个接口,用于创建相关或依赖对象的族,而无需指定其具体类”
如果你不明白这句话,不要担心这是正常的。它不是无缘无故地被称为抽象工厂!
如果它能帮助你,我认为抽象的工厂模式是工厂方法模式的推广,这次工厂接口有多个相关的工厂方法。当我说相关时,我的意思是概念上的链接,以便它们形成工厂方法的“家庭”。让我们看一下图,看看与工厂方法模式的区别:
由于这是非常荒谬的,让我们回到CarFactory的例子。
使用工厂方法模式,工厂接口只有一个方法,getCar()。抽象工厂可以是具有3种工厂方法的接口:getEngine(),getBody()和getWheel()。您可以拥有多个混凝土工厂:
如果你想制造一辆跑车,你需要先加入一个SportCarFactory,然后使用它。如果你想制造一辆便宜的汽车,你需要实例化一个廉价汽车工厂,然后使用它。
这个抽象工厂的3个工厂方法是相关的。它们都属于汽车生产概念。
当然,工厂方法可以具有参数,以便它们返回不同的类型。例如,SportCarFactory的getEngine(String模型)工厂可以返回Ferrari458Engine或FerrariF50Engine或Ferrari450Engine或…取决于参数。
这是java中的相同示例(仅使用SportCarFactory和2个工厂方法)。
/the different products public interface Wheel{ public void turn(); } public class RaceCarWheel implements Wheel{ public void turn(){ // some stuff } ... } public interface Engine{ public void work(); } public class PowerfulEngine implements Engine{ public void work(){ // some stuff } ... } /the factory public interface CarFactory{ public Engine getEngine(); public Wheel getWheel(); } public class SportCarFactory implements CarFactory{ public Engine getEngine(){ return new PowerfulEngine(); } public Wheel getWheel(){ return new RaceCarWheel(); } } /some code using the factory public class SomeClass { public void someFunctionInTheCode(){ CarFactory carFactory = new SportCarFactory(); Wheel myWheel= carFactory.getWheel(); Engine myEngine = carFactory.getEngine(); } }
这个工厂并不容易。 那么,什么时候应该使用它?
~~从不!!!!~~嗡嗡作响,难以回答。我认为这种工厂模式是组织代码的一种方式。如果你最终在代码中有许多工厂方法模式,并且你看到一些它们之间的共同主题,你可以用一个抽象工厂收集这个组。我不是“让我们使用抽象工厂,因为我们将来可能需要一个”的忠实粉丝,因为这种模式非常抽象。我更喜欢构建简单的东西,并在需要时重构它们。
然而,一个常见的用例是当您需要创建具有不同外观和感觉的用户界面时。这个例子被四人帮用来呈现这种模式。这个UI将需要一些产品,如窗口,滚动条,按钮…您可以为每种外观创建具有混凝土工厂的工厂。当然,这个例子是在互联网时代之前写的,现在你可以有一个组件,并使用CSS(或一些脚本语言)修改它的外观和感觉,即使对于桌面应用程序也是如此。这意味着静态工厂方法在大多数情况下就足够了。
但是,如果您仍然想使用此模式,以下是 GoF 中的一些用例:
“系统应配置多个产品系列之一”
“你想提供一个产品的类库,你只想揭示它们的接口,而不是它们的实现”
大多数 DAO(数据访问对象)框架使用 abtract 工厂来指定具体工厂应该执行的基本操作。虽然工厂方法的名称取决于框架,但它通常关闭于:
对于您操作的每种类型的对象,都需要一个具体的工厂。例如,如果您需要使用数据库管理人员、房屋和合同。我将有一个PersonFactory,一个HouseFactory和一个ContractFactory。
来自Spring的CrudRepository是抽象工厂的一个很好的例子。
如果你想要Java代码,你可以寻找JPA,Hibernate或SpringData教程。
我希望您现在对不同类型的工厂模式以及何时使用它们有了很好的了解。虽然我在本文中说过很多次,但请记住,大多数时候工厂会使代码更加复杂/抽象。即使你知道工厂(如果你不知道,再读一遍这篇文章!),你的同事呢?然而,在处理中型/大型应用程序时,值得使用工厂。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。