你是否曾被对象创建的繁琐细节所困扰?今天就让我们一起探索工厂模式,看它如何帮你化繁为简!
一、前序:生活中无处不在的"工厂"
周末,我站在星巴克的柜台前。
"一杯焦糖玛奇朵,中杯,少冰。"
几分钟后,一杯香气四溢的咖啡便出现在我手中。在这个过程中,我只需告知咖啡名称和一些简单的定制要求,无需了解牛奶的比例、咖啡豆的研磨程度,甚至不需要亲自操作复杂的咖啡机。
这不正是工厂模式的生动体现吗?
再想想手机APP上点外卖、在4S店提车、在IKEA下单一张桌子...这些场景中,我们都在扮演"客户端"的角色,只需发出请求、表达需求,而具体的生产过程则被巧妙封装,由专业的"工厂"代为处理。
作为程序员,我们不也经常面临类似的问题吗?如何让对象的创建变得简单而优雅?如何在保持代码灵活性的同时,避免创建对象时的各种依赖耦合?
这正是工厂模式要解决的核心问题。
日常开发中,我们创建对象的方式往往都比较直接,就像下面这样:
// 处理支付宝支付
if ("alipay".equals(payType)) {
AlipayService alipayService = new AlipayService();
alipayService.setProp1("支付宝特有属性");
return alipayService.doPay(order);
}
// 处理微信支付
else if ("wechat".equals(payType)) {
WechatPayService wechatPayService = new WechatPayService();
wechatPayService.setAppId("wx123456");
return wechatPayService.pay(order);
}
// 处理银联支付
else if ("unionpay".equals(payType)) {
UnionPayService unionPayService = new UnionPayService();
unionPayService.setMerchantId("merchant123");
return unionPayService.process(order);
}
// 可能还有更多支付方式...
这种方式有什么大问题嘛?其实好像也没什么问题,在有些时候反而会更简洁明了,但是在一些复杂场景或复杂业务中我们就不好对其进行业务扩展了。
这种直接创建对象并进行条件判断的方式,看似简单,实则埋下了不少隐患::
-
高耦合:客户端代码与具体实现类紧密耦合
-
难以维护:每增加一种支付方式,就需要修改这段判断逻辑
-
代码重复:如果多个地方需要创建支付服务对象,这些判断逻辑就会重复出现
-
违反开闭原则:对扩展开放,对修改关闭的原则被违反
-
测试困难:条件判断逻辑增加了单元测试的复杂性
这时,我们就需要一种更优雅的设计模式来解决这个问题。
二、工厂模式:创建型设计模式中的明星
2.1 什么是工厂模式?
工厂模式(Factory Pattern)是创建型设计模式的一种,它提供了一种创建对象的最佳方式,将对象的创建与使用分离,让对象的创建变得优雅。
在GoF(Gang of Four,四人帮)的设计模式中,工厂模式主要分为三类:
- 简单工厂模式(Simple Factory Pattern)
- 工厂方法模式(Factory Method Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
这三种模式像是工厂的三个进化阶段,随着复杂度的增加而逐步升级。
2.2 简单工厂模式:万物起源
简单工厂模式,顾名思义,是最简单的工厂模式。它通过一个工厂类根据传入的参数不同返回不同的实例,被创建的实例通常都具有共同的父类或接口。
来看一个例子:
// 通用支付接口
public interface Payment {
void pay();
void transfer();
}
// 微信
public class WxPayment implements Payment {
@Override
public void pay() {
System.out.println("正在使用微信支付...");
}
@Override
public void transfer() {
System.out.println("正在使用微信转账...");
}
}
// 支付宝
public class AliPayment implements Payment {
@Override
public void pay() {
System.out.println("正在使用支付宝支付...");
}
@Override
public void transfer() {
System.out.println("正在使用支付宝转账...");
}
}
// 简单工厂类
public class SimplePaymentFactory {
public static Payment createPayment(String type) {
if ("WEIXIN".equalsIgnoreCase(type)) {
return new WxPayment();
} else if ("ALIPAY".equalsIgnoreCase(type)) {
return new AliPayment();
}
throw new RuntimeException("当前的支付类型不支持");
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
// 向工厂获取微信支付客户端
WxPayment weixin = SimplePaymentFactory.createPayment("WEIXIN");
weixin.pay();
weixin.transfer();
// 向工厂获取阿里支付客户端
AliPayment alipay = SimplePaymentFactory.createPayment("ALIPAY");
alipay.pay();
alipay.transfer();
}
}
简单工厂的特点:
- 将创建对象的代码集中在一个工厂类中,客户端无需关心对象的创建细节
- 客户端只需要知道所需对象的名称即可
- 如果需要添加新的产品,需要修改工厂类,违反了"开闭原则"
2.3 工厂方法模式:多态的魅力
简单工厂的最大问题是:每增加一个产品,都需要修改工厂类。如果产品种类非常多,工厂类的代码将变得臃肿且难以维护。
工厂方法模式通过定义一个创建对象的接口,让子类决定实例化哪一个类,工厂方法让类的实例化推迟到子类中进行。
// 支付工厂接口
public interface PayFactory {
Payment createPayment();
}
// 微信支付工厂
public class WxPayFactory implements PayFactory {
@Override
public Payment createPayment() {
return new WxPayment();
}
}
// 阿里支付工厂
public class AliPayFactory implements PayFactory {
@Override
public Payment createPayment() {
return new AliPayment();
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
//创建微信支付工厂
PayFactory wxPayFactory = new WxPayFactory();
Payment wxPayment = wxPayFactory.createPayment();
wxPayment.pay();
wxPayment.transfer();
//创建支付宝支付工厂
PayFactory aliPayFactory = new AliPayFactory();
Payment aliPayment = aliPayFactory.createPayment();
aliPayment.pay();
aliPayment.transfer();
}
}
工厂方法的特点:
- 符合开闭原则,新增产品只需添加对应的工厂类
- 每个产品都有对应的工厂类,增加了系统复杂度
- 更加灵活,但也带来了更多的类
2.4 抽象工厂模式:产品族的概念
当我们需要创建一系列相关或相互依赖的对象时,抽象工厂模式就派上用场了。它提供一个接口用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。
回顾一下,在日常支付对接过程中,每家支付商都有自己的配置文件、密钥以及URL等,我们的支付对接代码中,需要根据支付商类型来获取对应的配置文件、密钥以及URL等:
// 支付配置接口
public interface PayConfig {
String getConfig();
}
// 微信支付配置
public class WxPayConfig implements PayConfig {
@Override
public String getConfig() {
return "wx_config";
}
}
// 支付配置接口
public class AliPayConfig implements PayConfig {
@Override
public String getConfig() {
return "ali_config";
}
}
// 抽象支付工厂接口
public interface PayFactory {
PayConfig createPayConfig();
Payment createPayment(PayConfig payConfig);
}
// 微信支付工厂
public class WxPayFactory implements PayFactory {
@Override
public PayConfig createPayConfig() {
return new WxPayConfig();
}
@Override
public Payment createPayment(PayConfig payConfig) {
return new WxPayment(payConfig);
}
}
// 阿里支付工厂接口
public class AliPayFactory implements PayFactory {
@Override
public PayConfig createPayConfig() {
return new AliPayConfig();
}
@Override
public Payment createPayment(PayConfig payConfig) {
return new AliPayment(payConfig);
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
PayFactory wxPayFactory = new WxPayFactory();
Payment wxPayment = wxPayFactory.createPayment(wxPayFactory.createPayConfig());
wxPayment.pay();
wxPayment.transfer();
PayFactory aliPayFactory = new AliPayFactory();
Payment aliPayment = aliPayFactory.createPayment(aliPayFactory.createPayConfig());
aliPayment.pay();
aliPayment.transfer();
}
}
朋友们,抽象工厂模式优不优雅?
抽象工厂的特点:
- 可以确保一系列相关产品的一致性
- 符合开闭原则,但增加新产品族比较困难
- 适合系统产品有多个产品族,而系统只消费其中某一族的产品的情况
2.5 静态工厂方法:Java的特殊实践
在Java的标准库中,我们经常能看到一种特殊的工厂模式实现——静态工厂方法。它通过公共的静态方法返回类的实例,而不是通过构造器。
例如,Integer.valueOf(),Calendar.getInstance()等方法都属于静态工厂方法。
// 自定义一个静态工厂方法
public class User {
private String name;
private int age;
private User(String name, int age) {
this.name = name;
this.age = age;
}
// 静态工厂方法
public static User createAdult(String name) {
return new User(name, 18);
}
public static User createChild(String name) {
return new User(name, 6);
}
// getter和setter
// ...
}
客户端代码:
public class Client {
public static void main(String[] args) {
User adult = User.createAdult("张三");
User child = User.createChild("李四");
}
}
静态工厂方法的优势:
- 比构造器更有描述性,名称可以准确传达创建对象的意图
- 不必每次调用时都创建新对象,可以返回缓存的实例
- 可以返回任何子类型的对象,更灵活
- 可以根据参数不同而返回不同类的对象
- 简化了客户端代码
三、深入理解工厂模式
3.1 工厂模式的核心思想
工厂模式的核心思想可以概括为:"封装变化,针对接口编程,而不是针对实现编程"。
它解决了以下几个问题:
- 封装对象创建的复杂性:客户端不需要了解对象创建的具体细节
- 降低耦合度:将对象的创建与使用分离
- 增强扩展性:符合开闭原则,便于添加新的产品类型
- 提高代码可维护性:集中管理对象的创建逻辑
3.2 工厂模式的适用场景
- 当一个类不知道它所需要的对象的类时
- 当一个类希望由其子类来指定它所创建的对象时
- 当类将创建对象的职责委托给多个帮助子类之一,并且您希望将哪一个帮助子类是代理者这一信息局部化时
3.3 各种工厂模式的对比
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 简单工厂 | 实现简单,客户端无需知道具体产品类 | 违反开闭原则,工厂类职责过重 | 产品种类较少且不常变化 |
| 工厂方法 | 符合开闭原则,单一职责原则 | 类的个数容易过多,增加系统复杂度 | 产品种类多,需要灵活扩展 |
| 抽象工厂 | 产品族概念,创建相关产品簇 | 难以增加新的产品等级结构 | 需要创建一系列相互关联的产品 |
| 静态工厂方法 | 简单直观,代码量少 | 不能继承,不能实现多态 | 创建对象的方式比较固定 |
四、实战:设计一个支付工厂
在实际开发中,支付系统是工厂模式的一个典型应用场景。假设我们需要实现一个支持支付宝、微信支付、银联等多种支付方式的系统:
4.1 定义支付接口
/**
* 支付接口
*/
public interface Payment {
/**
* 初始化支付环境
*/
void init();
/**
* 支付操作
* @param amount 支付金额
* @return 支付结果
*/
boolean pay(double amount);
}
4.2 实现各种支付方式
/**
* 支付宝支付
*/
public class AliPay implements Payment {
@Override
public void init() {
System.out.println("初始化支付宝支付环境...");
}
@Override
public boolean pay(double amount) {
System.out.println("支付宝支付 " + amount + " 元");
// 实际支付逻辑...
return true;
}
}
/**
* 微信支付
*/
public class WeChatPay implements Payment {
@Override
public void init() {
System.out.println("初始化微信支付环境...");
}
@Override
public boolean pay(double amount) {
System.out.println("微信支付 " + amount + " 元");
// 实际支付逻辑...
return true;
}
}
/**
* 银联支付
*/
public class UnionPay implements Payment {
@Override
public void init() {
System.out.println("初始化银联支付环境...");
}
@Override
public boolean pay(double amount) {
System.out.println("银联卡支付 " + amount + " 元");
// 实际支付逻辑...
return true;
}
}
4.3 使用工厂方法模式实现支付工厂
/**
* 支付工厂接口
*/
public interface PaymentFactory {
Payment createPayment();
}
/**
* 支付宝工厂
*/
public class AliPayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new AliPay();
}
}
/**
* 微信支付工厂
*/
public class WeChatPayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new WeChatPay();
}
}
/**
* 银联支付工厂
*/
public class UnionPayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new UnionPay();
}
}
4.4 客户端使用
public class PaymentClient {
public static void main(String[] args) {
// 创建支付宝支付
PaymentFactory aliPayFactory = new AliPayFactory();
Payment aliPay = aliPayFactory.createPayment();
aliPay.init();
aliPay.pay(100.0);
// 创建微信支付
PaymentFactory weChatPayFactory = new WeChatPayFactory();
Payment weChatPay = weChatPayFactory.createPayment();
weChatPay.init();
weChatPay.pay(200.0);
// 创建银联支付
PaymentFactory unionPayFactory = new UnionPayFactory();
Payment unionPay = unionPayFactory.createPayment();
unionPay.init();
unionPay.pay(300.0);
}
}
4.5 优化:结合简单工厂
我们还可以结合简单工厂模式,让系统更加简洁:
/**
* 支付类型枚举
*/
public enum PaymentType {
ALI_PAY,
WECHAT_PAY,
UNION_PAY
}
/**
* 简单支付工厂
*/
public class SimplePaymentFactory {
public static Payment createPayment(PaymentType type) {
switch (type) {
case ALI_PAY:
return new AliPay();
case WECHAT_PAY:
return new WeChatPay();
case UNION_PAY:
return new UnionPay();
default:
throw new IllegalArgumentException("不支持的支付类型");
}
}
}
客户端使用:
public class PaymentClient {
public static void main(String[] args) {
// 创建支付宝支付
Payment aliPay = SimplePaymentFactory.createPayment(PaymentType.ALI_PAY);
aliPay.init();
aliPay.pay(100.0);
// 创建微信支付
Payment weChatPay = SimplePaymentFactory.createPayment(PaymentType.WECHAT_PAY);
weChatPay.init();
weChatPay.pay(200.0);
// 创建银联支付
Payment unionPay = SimplePaymentFactory.createPayment(PaymentType.UNION_PAY);
unionPay.init();
unionPay.pay(300.0);
}
}
4.6 反思与改进
在实际开发中,我们还可以结合配置文件或注解,使工厂模式更加灵活:
/**
* 支付工厂(结合反射机制实现)
*/
public class ReflectPaymentFactory {
private static Map<String, Class<? extends Payment>> paymentMap = new ConcurrentHashMap<>();
// 在静态代码块中注册支付方式
static {
paymentMap.put("alipay", AliPay.class);
paymentMap.put("wechat", WeChatPay.class);
paymentMap.put("unionpay", UnionPay.class);
}
public static Payment createPayment(String type) {
try {
Class<? extends Payment> paymentClass = paymentMap.get(type.toLowerCase());
if (paymentClass == null) {
throw new IllegalArgumentException("不支持的支付类型:" + type);
}
// 通过反射创建支付对象
return paymentClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("创建支付对象失败", e);
}
}
// 注册新的支付方式
public static void registerPayment(String type, Class<? extends Payment> paymentClass) {
paymentMap.put(type.toLowerCase(), paymentClass);
}
}
使用反射机制后,可以轻松添加新的支付方式而无需修改工厂类:
import org.springframework.context.annotation.Configuration;
// 添加新的支付方式:PayPal
public class PayPal implements Payment {
@Override
public void init() {
System.out.println("初始化PayPal支付环境...");
}
@Override
public boolean pay(double amount) {
System.out.println("PayPal支付 " + amount + " 元");
return true;
}
}
// 在应用启动时注册
ReflectPaymentFactory.registerPayment("paypal", PayPal.class);
// 使用
Payment paypal = ReflectPaymentFactory.createPayment("paypal");
paypal.init();
paypal.pay(500.0);
在Java 8+的函数式编程中,工厂模式也有了新的实现方式:
// 使用Lambda表达式和函数式接口
public class FunctionalPaymentFactory {
private static Map<String, Supplier<Payment>> paymentMap = new HashMap<>();
static {
paymentMap.put("alipay", AliPay::new);
paymentMap.put("wechat", WeChatPay::new);
paymentMap.put("unionpay", UnionPay::new);
}
public static Payment createPayment(String type) {
Supplier<Payment> supplier = paymentMap.get(type.toLowerCase());
if (supplier == null) {
throw new IllegalArgumentException("不支持的支付类型:" + type);
}
return supplier.get();
}
// 注册新的支付方式
public static void registerPayment(String type, Supplier<Payment> supplier) {
paymentMap.put(type.toLowerCase(), supplier);
}
}
随着Spring等框架的普及,我们也可以借助依赖注入容器实现更加优雅的工厂模式:
@Configuration
public class PaymentConfig {
@Bean
public Map<String, Payment> paymentMap() {
Map<String, Payment> map = new HashMap<>();
map.put("alipay", new AliPay());
map.put("wechat", new WeChatPay());
map.put("unionpay", new UnionPay());
return map;
}
@Bean
public PaymentFactory paymentFactory(Map<String, Payment> paymentMap) {
return new SpringPaymentFactory(paymentMap);
}
}
public class SpringPaymentFactory {
private final Map<String, Payment> paymentMap;
public SpringPaymentFactory(Map<String, Payment> paymentMap) {
this.paymentMap = paymentMap;
}
public Payment getPayment(String type) {
Payment payment = paymentMap.get(type.toLowerCase());
if (payment == null) {
throw new IllegalArgumentException("不支持的支付类型:" + type);
}
return payment;
}
}
五、总结与展望
工厂模式的核心要点
- 关注点分离:将对象的创建与使用分开,让客户端专注于业务逻辑
- 封装变化:封装了对象创建的逻辑,使系统更容易扩展
- 开闭原则:尤其是工厂方法模式,很好地体现了开闭原则
- 依赖倒置原则:客户端依赖抽象接口,而非具体实现类
在所有项目中合适的永远才是最好的,所以我们在使用工厂模式时,需要遵循以下几点:
- 选择合适的工厂模式:根据实际需求选择简单工厂、工厂方法或抽象工厂
- 避免过度设计:有时候简单工厂或静态工厂方法就够用了
- 与其他模式结合:工厂模式常与单例模式、策略模式等结合使用
- 注意性能问题:反射虽然灵活,但性能较低,需谨慎使用
参考资料:
- 《重学Java设计模式》
- 《芋道源码》
下一篇预告
在下一篇中,我们将探讨另一个重要的设计模式——策略模式。策略模式与工厂模式有很多相似之处,但解决的问题不同。我们将学习如何在实际开发中灵活运用策略模式,使代码更具弹性和可维护性。
最后,希望朋友们看完这篇文章会有有一点点启发与借鉴意义,我会继续努力,输出更多我对设计模式的理解。