参考
- 工厂模式 - pdai
- 工厂方法详解
- 工厂模式快速入门
- 《图解设计模式》 ------ Factory Method
快速入门
主要作用
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。
工厂方法模式的本质
延迟到 子类 来 选择实现
简单工厂存在的问题
- 工厂类集中产品创建逻辑,若工厂无法正常工作,系统受影响。
- 违背"开放 - 关闭原则",添加新产品需改工厂类逻辑致其复杂。简单工厂模式用静态方法,不能被继承重写,工厂角色难形成基于继承的等级结构。
简单示例
工厂抽象类 Factory
java
abstract class Factory {
abstract Product factoryMethod();
abstract void showProductName();
}
具体工厂类
java
public class ConcreteFactoryA extends Factory{
@Override
Product factoryMethod() {
return new ConcreteProductA();
}
@Override
void showProductName() {
System.out.println(this.factoryMethod().getProductName());
System.out.println(this.getClass().getSimpleName() + " do something ....");
}
}
java
public class ConcreteFactoryB extends Factory {
@Override
Product factoryMethod() {
return new ConcreteProductB();
}
@Override
void showProductName() {
System.out.println(this.factoryMethod().getProductName());
System.out.println(this.getClass().getSimpleName() + " do something ....");
}
}
抽象商品接口
java
public interface Product {
String getProductName();
}
具体商品接口
java
public class ConcreteProductA implements Product{
@Override
public String getProductName() {
return "Product A";
}
}
public class ConcreteProductB implements Product{
@Override
public String getProductName() {
return "Product B";
}
}
客户类
java
public class Client {
public static void main(String[] args) {
Factory factory = new ConcreteFactoryA();
String factoryProductName = factory.factoryMethod().getProductName();
System.out.println(factoryProductName);
factory.showProductName();
factory = new ConcreteFactoryB();
factoryProductName = factory.factoryMethod().getProductName();
System.out.println(factoryProductName);
factory.showProductName();
/*
输出:
Product A
ConcreteFactoryA do something ....
Product B
ConcreteFactoryB do something ....
*/
}
}
优缺点
优点
-
更加符合开闭原则:
新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可
不需要像简单工厂模式那样修改工厂类的判断逻辑 -
符合单一职责原则:
每个具体工厂类只负责创建对应的产品
,简单工厂中的工厂类存在复杂的switch
逻辑判断 -
不使用静态工厂方法,可以形成基于继承的等级结构;而简单工厂模式使用了静态工厂方法
缺点
- 添加新产品需增加对应工厂类,系统类成对增加,增加
复杂度与开销
。 - 考虑可扩展性引入
抽象层
,增加抽象性和理解难度,实现可能用 DOM、反射等技术,增加实现难度。 - 虽保证工厂方法内对修改关闭,但更换产品仍需
修改具体工厂类
。一个具体工厂只能创建一种具体产品。
总结:工厂模式是简单工厂模式的抽象拓展,保留封装优点,使扩展简单、继承可行且增加多态性体现。
入门案例
设计一个 Card 开户系统,实现制作身份证 (IDCard)的作用
Product
类和 Factory 类属于 framework 包。组成了生成实例的框架
IDCard
类和 IDCardFactory 类负责实际的加工处理,它们都属于 idcard 包
Client
类是用于测试程序行为的类
类图设计 UML
核心组件
- Product (产品)
Product 角色属框架方抽象类,定义工厂方法模式生成示例的 API,具体处理由子类ConcreteProduct 角色确定。。
- Creator (创建者)
框架一方生成 Product 角色抽象类,具体处理由 ConcreteCreator 决定。调用 Product 角色及生成实例方法可生成 Product 实例,非用 new 关键字,而是调用专用方法,防止父类与其他具体类耦合。。
- ConcreteCreator (具体的创建者)
具体加工的一方,决定了具体的产品。
- ConcreteProduct ( 具体的创建者 )
ConcreteCreator 角色属于具体加工的一方。
代码实现
- 新建
framework
包:放置框架相关抽象类 - 新建
idcard
包:存放具体的加工实现类
Product
仅声明了 use 抽象方法。use 方法的实现则被交给了 Product 类的子类负责
java
package com.jools.designpattern.create.factory.framework;
public abstract class Product {
/**
* 抽象方法 use; 定义了产品是 "任意可以 user 的" 东西
*/
public abstract void user();
}
Factory 类 (抽象)
借助模板设计模式
声明用于 生成产品
的 createProduct
抽象方法和用于 注册产品
的 registerProduct
抽象方法。
- 生成产品
- 注册产品
具体实现交给 Factory 子类负责。
生成具体 Product
类的逻辑:
- 先调用
createProduct
生成产品 - 接着调用
registerProduct
注册产品
具体的实现内容根据工厂方法模式适用的场景不同而不同
java
package com.jools.designpattern.create.factory.framework;
public abstract class Factory {
public final Product create(String owner) {
Product product = createProduct(owner);
registerProduct(product);
return product;
}
/**
* 生成产品
*
* @param owner 账户姓名
* @return
*/
protected abstract Product createProduct(String owner);
/**
* 注册产品
*
* @param product
* @return
*/
protected abstract void registerProduct(Product product);
}
IDCard 类
负责加工处理的一类;实现了抽象的 Product 接口
java
package com.jools.designpattern.create.factory.idcard;
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
System.out.println("制作" + owner + " 的 ID 卡");
this.owner = owner;
}
@Override
public void use() {
System.out.println("使用了" + owner + " 的 ID 卡");
}
public String getOwner() {
return owner;
}
}
注意:IDCard 的构造函数并不是 public; 表示仅可以在同包下访问。体现了 '框架' 包和 '具体实现' 包下的解耦
IDCardFactory 类
实现了 createProduct
和 registerProduct
方法
createProduct
方法通过生成IDCard
的实例来生产产品registerProduct
方法则通过将IDCard
的 owner 持有人保存到owners
字段来实现注册产品
java
package com.jools.designpattern.create.factory.idcard;
public class IDCardFactory extends Factory {
private List<String> owners = new ArrayList<>();
@Override
protected Product createProduct(String owner) {
return new IDCard(owner);
}
@Override
protected void registerProduct(Product product) {
owners.add(((IDCard) product).getOwner());
}
//返回账户
public List<String> getOwners() {
return owners;
}
}
测试 - 客户端类
java
package com.jools.designpattern.create.factory;
public class IDCardClient {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("AAA");
Product card2 = factory.create("BBB");
Product card3 = factory.create("CCC");
card1.use();
card2.use();
card3.use();
List<String> owners = ((IDCardFactory) factory).getOwners();
for (String name : owners) {
System.out.println(name);
}
/*
输出:
制作AAA 的 ID 卡
制作BBB 的 ID 卡
制作CCC 的 ID 卡
使用了AAA 的 ID 卡
使用了BBB 的 ID 卡
使用了CCC 的 ID 卡
AAA
BBB
CCC
*/
}
}
扩展思路
- "框架" 和 "具体加工" 两方面内容分别被封装到
framework
和idcard
包中 - 举例使用
产品
和工厂
比如要创建表示电视机类的 Television
和表示电视机厂的 TelevisonFactory
。 这时我们仅需要引入 framework
包就可以进一步编写具体实现类的 television
包
(补充)生成具体实现的三种方式
Factory 类的 createProduct
方法是抽象方法,也就是说需要在子类中实现该方法。
createProcut 方法的实现方式一般有以下 3 种
指定其为抽象方法
劣势: 一旦定义为抽象方法,子类就必须实现该方法。如果子类不实现该方法,编译器就会报告编译错误。
java
abstract class Factory {
public abstract Product createProduct(String name) {
}
}
为其实现默认处理
如果子类没有实现该方法,将进行默认的实现
劣势: 但是,这时使用 new 关键字,因此不能将 Product 类定义为抽象类; 仅能切换为接口
java
class Factory {
public Product createProduct(String name) {
return new Product(name);
}
}
在其中抛出异常
createProduct
方法的默认处理为抛出异常,这样一来,如果未在子类中实现该方法,程序就会在运行时报错
java
class Factory {
public Product createProduct(String name) {
throw new FactoryMethodRuntimeException(); //可自定义实现异常类
}
}
思考问题
一、为什么 IDCard 类的构造函数不是 public?
java
package com.jools.designpattern.create.factory.idcard;
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
....
}
}
个人见解:
保证具体实现类仅能具体实现类包
即: idcard
访问实现 framework 和 具体实现层 隔离
防止抽象框架层和具体实现耦合
二、修改为 IDCard 添加卡的编号,并且在 IDCardFactory 类中保存编号与所有者之间的对应表
仅需要修改具体实现类
修改 IDCard
java
package com.jools.designpattern.create.factory.idcard;
public class IDCard extends Product {
private String owner;
//新增编号
private String no;
IDCard(String owner, String no) {
System.out.println("制作" + owner + " 的 ID 卡 ------ " + " 编号:" + no);
this.owner = owner;
this.no = no;
}
@Override
public void use() {
System.out.println("使用了" + owner + " 的 ID 卡");
}
public String getOwner() {
return owner;
}
//新增getter
public String getNo() {
return no;
}
}
修改 IDCardFactory
java
package com.jools.designpattern.create.factory.idcard;
public class IDCardFactory extends Factory {
//保存账户人名
private List<String> owners = new ArrayList<>();
//新增: 保存账户和编号的对应
private Map<String, String> noMapper = new HashMap<>();
Random random = new Random();
//新增: getter
public Map<String, String> getNoMapper() {
return noMapper;
}
@Override
protected Product createProduct(String owner) {
return new IDCard(owner, String.valueOf(random.nextInt(10000)));
}
@Override
protected void registerProduct(Product product) {
owners.add(((IDCard) product).getOwner());
//注册编号 - owner 映射
noMapper.put(((IDCard) product).getNo(), ((IDCard) product).getOwner());
}
//返回账户
public List<String> getOwners() {
return owners;
}
}
测试
java
package com.jools.designpattern.create.factory;
public class IDCardClient {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("AAA");
Product card2 = factory.create("BBB");
Product card3 = factory.create("CCC");
card1.use();
card2.use();
card3.use();
List<String> owners = ((IDCardFactory) factory).getOwners();
for (String name : owners) {
System.out.println(name);
}
Map<String, String> noMapper = ((IDCardFactory) factory).getNoMapper();
for (String no : noMapper.keySet()) {
System.out.println("编号:" + no + " - 用户:" + noMapper.get(no));
}
/*
制作AAA 的 ID 卡 ------ 编号:2694
制作BBB 的 ID 卡 ------ 编号:7127
制作CCC 的 ID 卡 ------ 编号:5388
使用了AAA 的 ID 卡
使用了BBB 的 ID 卡
使用了CCC 的 ID 卡
AAA
BBB
CCC
编号:7127 - 用户:BBB
编号:5388 - 用户:CCC
编号:2694 - 用户:AAA
*/
}
}
总结
使用场景
- 客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可
- 创建对象的任务委托给多个工厂子类中的一个,客户端无须关心哪一个工厂的子类创建产品
工厂模式的本质
延迟到子类来选择实现
解决的问题
- 解决简单工厂模式的缺点:工厂需要生产新的产品不需要再修改工厂类中的对应逻辑,符合 "开闭" 原则
- 具体的产品创建延迟到了工厂类的子类中,此时工厂类不再负责所有产品的创建。
体现的设计模式原则
- 开闭原则:只增加一种产品的时候,只需要增加相应的具体产品类的相应的工厂子类即可
- 单一职责:每个具体工厂类只负责创建对应的产品
- 封装性和扩展性:保留了简单工厂的封装优点,提升了扩展性
- 依赖倒置:高层组件不应依赖低层组件,二者都应依赖抽象。倒置的是这个接口的 "所有权",底层实现的接口的所有权并不在底层组件上,而是倒置到高层组件中去
存在缺陷
- 耦合度:添加新产品类需提供对应工厂类,系统类个数成对增加,提升系统复杂度。
- 引入了抽象层,增加了系统复杂度
- 工厂方法保证内部修改关闭,但使用工厂方法的类若更换产品,仍需修改实例化的具体工厂类。
何时选用工厂模式?
- 如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类去实现。
- 如果一个类本身就希望,由它的子类来创建所需的对象的时候,应该使用工厂方法模式。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用的时候可以无无须关系哪个工厂子类创建产品子类,需要时再动态指定
更多案例
场景问题 01
考虑 实现导出数据应用框架,供客户选导出方式并执行 。企业系统常分散,公司无专网实力,不愿数据实时在广域网传,因安全和速度。有折中方案,分公司系统独立在局域网运行,每日业务结束导出数据传总公司或专人送。系统导出有约定方式,如文本、数据库备份、Excel、Xml 格式 等。现考虑实现此框架。
解决场景问题
java
/**
* 导出的文件对象的接口
*/
public interface ExportFileApi {
/**
* 导出内容成为文件
* @param data 示意:需要保存的数据
* @return 是否导出成功
*/
public boolean export(String data);
}
对于实现导出数据的业务功能对象,它需要创建ExportFileApi的具体实例对象,但是它只知道ExportFileApi接口,而不知道其具体的实现。
工厂方法的定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类,
Factory Method
使得一个类的实例化延迟到子类
应用工厂方法模式的思路
在实现导出数据的业务功能对象中,不知要用哪种导出文件格式,所以不应与具体导出文件对象耦合,只需面向其接口。
问题:接口是不能直接使用的,需要使用具体的接口实现对象的实例
定义方法创建,它不知如何创建接口对象,定义成抽象方法让子类实现,这样对象可面向接口编程,无需关心如何创建接口对象。
简化结构图
解释
- Product: 定义工厂方法所创建的对象的接口,实际需要使用的对象的接口
- ConcreteProduct: 具体的 Product 接口的实现对象
- Creator: 创建器,声明工厂方法,工厂方法通常会返回一个
Product
类型的实例对象,而且多是抽象方法。也可以在Creator
里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product
- ConcreteCreator: 具体的创建器对象,覆盖实现
Creator
定义的工厂方法,返回具体的Product
实例
使用工厂模式实现解决方法
定义抽象 ExportFileApi
java
/**
* @description: 导出的文件对象的接口
*/
public interface ExportFileApi {
boolean export(String data);
}
接口 ExportFileApi
的具体实现
java
public class ExportTxtFile implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据:" + data + " 到文本文件内");
return true;
}
}
public class ExportDB implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据" + data + " 到数据库进行备份");
return true;
}
}
定义抽象接口 ExportOperate
java
public abstract class ExportOperate {
/**
* 导出文件
*/
public boolean export(String data) {
//使用工厂方法
ExportFileApi exportFileApi = factoryMethod();
return exportFileApi.export(data);
}
protected abstract ExportFileApi factoryMethod();
}
Creator 的具体实现类
java
public class ExportTextFileOperate extends ExportOperate {
@Override
protected ExportFileApi factoryMethod() {
//创建导出成文本文件格式的对象
return new ExportTxtFile();
}
}
public class ExportDBOperate extends ExportOperate {
@Override
protected ExportFileApi factoryMethod() {
//创建导出数据库备份文件形式的对象
return new ExportDB();
}
}
客户端可以基于需要使用的 Creator 对象,调用相应的方法
java
public class Client {
public static void main(String[] args) {
ExportOperate operate = new ExportDBOperate();
//Operate 抽象类中 export 方法调用 factoryMethod 返回 ExportFileApi 接口实现类完成
operate.export("测试数据");
/*
输出: 导出数据测试数据 到数据库进行备份
*/
}
}
时序图
01 - 客户端使用 Creator 创建出来的对象情况的调用给顺序示意图
02 - 客户端使用 Creator 对象的时候的调用顺序
依赖注入和控制反转是同一个概念吗?
不是,描述的角度不一致
- 依赖注入是从应用程序的角度在描述
- 而控制反转是从容器的角度在描述
小结:工厂方法模式与 IoC/DI 思想在思想层面相似,皆为 主动变被动
进行了 主从换位
,以获更灵活程序结构。