聊聊工厂
说说3种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。
工厂模式的定义: 定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
1.简单工厂
优点和缺点:
优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可。
- 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点:
- 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
- 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
应用场景
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
印象中的实现
public class Client { public static void main(String[] args) { } //抽象产品 public interface Product { void show(); } //具体产品:ProductA static class ConcreteProduct1 implements Product { public void show() { System.out.println("具体产品1显示..."); } } //具体产品:ProductB static class ConcreteProduct2 implements Product { public void show() { System.out.println("具体产品2显示..."); } } final class Const { static final int PRODUCT_A = 0; static final int PRODUCT_B = 1; static final int PRODUCT_C = 2; } static class SimpleFactory { public static Product makeProduct(int kind) { switch (kind) { case Const.PRODUCT_A: return new ConcreteProduct1(); case Const.PRODUCT_B: return new ConcreteProduct2(); } return null; } } }
进阶实现 见 com.nxhz.sy.mode.creation.factory.NumberFactory
public interface NumberFactory { //实现方式一 Number parse(String s); NumberFactory impl = new NumberFactoryImpl(); // 获取工厂实例: static NumberFactory getFactory() { return impl; } //实现方式二 /** * 简单的类型转换不一定要用工厂,只是一种思想 * 这样做的好处是可以从如下方法里面进行操作,不一定每次都要新增对象,也可以写缓存代码直接返回 * * 类似于Integer.valueOf(100) 源码操作; */ public static Number parse2(String s) { return new BigDecimal(s); } }
使用新特性interface中可以写方法,变量,可以直接在父接口获取实例,可以通过多态来实现创建不同类型的实例,可以在方法中抽象具体业务类,这样做的好处是允许创建产品的代码独立地变换,而不会影响到调用方,类似Integer.valueOf,有兴趣可以看看源码。
这种简化的使用静态方法创建产品的方式称为静态工厂方法,这种使用方式在java中普遍调用类似Integer.valueOf,看源码好处是有时可以直接在缓存中返回,有时会新建对象,提高了代码执行效率,且不一定每次都会真正创建对象,完全可以返回缓存的对象,从而提升速度并减少内存消耗。
可以学习这种设计思想,提高代码执行效率。
这里有个常用方法了里氏替换原则:List.of()
List<String> list = List.of("A", "B", "C");
这个静态工厂方法接收可变参数,然后返回`List`接口。需要注意的是,调用方获取的产品总是`List`接口,而且并不关心它的实际类型。即使调用方知道`List`产品的实际类型是`java.util.ImmutableCollections$ListN`,也不要去强制转型为子类,因为静态工厂方法`List.of()`保证返回`List`,但也完全可以修改为返回`java.util.ArrayList`。这就是里氏替换原则:返回实现接口的任意子类都可以满足该方法的要求,且不影响调用方。
总是引用接口而非实现类,能允许变换子类而不影响调用方,即尽可能面向抽象编程.
2.工厂方法模式
优缺点应用场景
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
简单工厂模式,提到了简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
应用场景:
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌
结构与实现
工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。本节来分析其基本结构和实现方法。
1. 结构
工厂方法模式的主要角色如下。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
2.实现
public class AbstractFactoryTest { public static void main(String[] args) { try { Product a; AbstractFactory af; af = (AbstractFactory) ReadXML1.getObject(); a = af.newProduct(); a.show(); } catch (Exception e) { System.out.println(e.getMessage()); } } } //抽象产品:提供了产品的接口 interface Product { public void show(); } //具体产品1:实现抽象产品中的抽象方法 class ConcreteProduct1 implements Product { public void show() { System.out.println("具体产品1显示..."); } } //具体产品2:实现抽象产品中的抽象方法 class ConcreteProduct2 implements Product { public void show() { System.out.println("具体产品2显示..."); } } //抽象工厂:提供了厂品的生成方法 interface AbstractFactory { public Product newProduct(); } //具体工厂1:实现了厂品的生成方法 class ConcreteFactory1 implements AbstractFactory { public Product newProduct() { System.out.println("具体工厂1生成-->具体产品1..."); return new ConcreteProduct1(); } } //具体工厂2:实现了厂品的生成方法 class ConcreteFactory2 implements AbstractFactory { public Product newProduct() { System.out.println("具体工厂2生成-->具体产品2..."); return new ConcreteProduct2(); } }
class ReadXML1 { //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象 public static Object getObject() { try { //创建文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File("src/FactoryMethod/config1.xml")); //获取包含类名的文本节点 NodeList nl = doc.getElementsByTagName("className"); Node classNode = nl.item(0).getFirstChild(); String cName = "FactoryMethod." + classNode.getNodeValue(); //System.out.println("新类名:"+cName); //通过类名生成实例对象并将其返回 Class<?> c = Class.forName(cName); Object obj = c.newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null; } } }
3.抽象工厂模式
模式的应用场景
抽象工厂模式最早的应用是用于创建属于不同操作系统的视窗构件。如 Java 的 AWT 中的 Button 和 Text 等构件在 Windows 和 UNIX 中的本地实现是不同的。
抽象工厂模式通常适用于以下场景:
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
模式的扩展
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
- 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
- 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
- 抽象工厂模式(Abstract Factory)是一个比较复杂的创建型模式。
- 抽象工厂模式和工厂方法不太一样,它要解决的问题比较复杂,不但工厂是抽象的,产品是抽象的,而且有多个产品需要创建,因此,这个抽象工厂会对应到多个实际工厂,每个实际工厂负责创建多个实际产品:
这种模式有点类似于多个供应商负责提供一系列类型的产品。我们举个例子:
假设我们希望为用户提供一个Markdown文本转换为HTML和Word的服务,它的接口定义如下:
public interface AbstractFactory {
// 创建Html文档:
HtmlDocument createHtml(String md);
// 创建Word文档:
WordDocument createWord(String md);
}
注意到上面的抽象工厂仅仅是一个接口,没有任何代码。同样的,因为HtmlDocument
和WordDocument
都比较复杂,现在我们并不知道如何实现它们,所以只有接口:
// Html文档接口:
public interface HtmlDocument {
String toHtml();
void save(Path path) throws IOException;
}
// Word文档接口:
public interface WordDocument {
void save(Path path) throws IOException;
}
这样,我们就定义好了抽象工厂(AbstractFactory
)以及两个抽象产品(HtmlDocument
和WordDocument
)。因为实现它们比较困难,我们决定让供应商来完成。
现在市场上有两家供应商:FastDoc Soft的产品便宜,并且转换速度快,而GoodDoc Soft的产品贵,但转换效果好。我们决定同时使用这两家供应商的产品,以便给免费用户和付费用户提供不同的服务。
我们先看看FastDoc Soft的产品是如何实现的。首先,FastDoc Soft必须要有实际的产品,即FastHtmlDocument
和FastWordDocument
:
public class FastHtmlDocument implements HtmlDocument {
public String toHtml() {
...
}
public void save(Path path) throws IOException {
...
}
}
public class FastWordDocument implements WordDocument {
public void save(Path path) throws IOException {
...
}
}
然后,FastDoc Soft必须提供一个实际的工厂来生产这两种产品,即FastFactory
:
public class FastFactory implements AbstractFactory {
public HtmlDocument createHtml(String md) {
return new FastHtmlDocument(md);
}
public WordDocument createWord(String md) {
return new FastWordDocument(md);
}
}
这样,我们就可以使用FastDoc Soft的服务了。客户端编写代码如下:
// 创建AbstractFactory,实际类型是FastFactory:
AbstractFactory factory = new FastFactory();
// 生成Html文档:
HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
html.save(Paths.get(".", "fast.html"));
// 生成Word文档:
WordDocument word = factory.createWord("#Hello\nHello, world!");
word.save(Paths.get(".", "fast.doc"));
如果我们要同时使用GoodDoc Soft的服务怎么办?因为用了抽象工厂模式,GoodDoc Soft只需要根据我们定义的抽象工厂和抽象产品接口,实现自己的实际工厂和实际产品即可:
// 实际工厂:
public class GoodFactory implements AbstractFactory {
public HtmlDocument createHtml(String md) {
return new GoodHtmlDocument(md);
}
public WordDocument createWord(String md) {
return new GoodWordDocument(md);
}
}
// 实际产品:
public class GoodHtmlDocument implements HtmlDocument {
...
}
public class GoodWordDocument implements HtmlDocument {
...
}
客户端要使用GoodDoc Soft的服务,只需要把原来的new FastFactory()
切换为new GoodFactory()
即可。
注意到客户端代码除了通过new
创建了FastFactory
或GoodFactory
外,其余代码只引用了产品接口,并未引用任何实际产品(例如,FastHtmlDocument
),如果把创建工厂的代码放到AbstractFactory
中,就可以连实际工厂也屏蔽了:
public interface AbstractFactory {
public static AbstractFactory createFactory(String name) {
if (name.equalsIgnoreCase("fast")) {
return new FastFactory();
} else if (name.equalsIgnoreCase("good")) {
return new GoodFactory();
} else {
throw new IllegalArgumentException("Invalid factory name");
}
}
}
参考博客地址: