工厂模式的核心是把对象创建逻辑从使用方中分离出来,但传统工厂通常将大量判断分支集中在一个方法里,类型越多分支越长。借助 Lambda 表达式,可以改用一个注册表保存每种类型对应的创建函数,让创建动作变成可动态配置的数据。这种方式不仅减少修改成本,还能在运行时注册或替换实现类。
一、传统工厂模式的局限
传统简单工厂一般定义一个公共接口或抽象类,然后通过传入的标识在工厂方法中匹配具体实现。例如创建消息处理器时,通常会写如下代码:
public class HandlerFactory {
public static Handler create(String type) {
if ("email".equals(type)) {
return new EmailHandler();
} else if ("sms".equals(type)) {
return new SmsHandler();
} else if ("push".equals(type)) {
return new PushHandler();
}
throw new IllegalArgumentException("unknown type: " + type);
}
}
这种实现有两个明显问题。第一,每新增一个 Handler 类型都要修改工厂方法,增加一条分支,分支膨胀后阅读体验变差,也容易漏改。第二,创建逻辑与判断逻辑绑定在一起,无法在运行时动态替换。例如需要根据不同环境切换短信通道时,只能继续加条件判断,或者写多个工厂实现类。
测试成本同样会随分支数量上升。测试人员需要覆盖每个分支,新增类型后还可能影响已有测试。虽然抽象工厂、工厂方法等变体可以缓解部分问题,但变量创建逻辑仍然分散在各处,离“动态配置”还有距离。
二、用 Lambda 表达式重构注册表工厂
Lambda 表达式允许将一段创建行为当作值传递。Java 自带的函数式接口 Supplier<T> 表示无参返回对象的逻辑,Function<T, R> 表示接收一个参数并返回结果。重构工厂时,可以把原先 new EmailHandler() 这样的创建语句封装成 Lambda 或方法引用,放入以类型名为 Key 的 Map 中。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public class LambdaHandlerFactory {
private final Map<String, Supplier<Handler>> registry = new ConcurrentHashMap<>();
public LambdaHandlerFactory() {
registry.put("email", EmailHandler::new);
registry.put("sms", SmsHandler::new);
registry.put("push", PushHandler::new);
}
public Handler create(String type) {
Supplier<Handler> supplier = registry.get(type);
if (supplier == null) {
throw new IllegalArgumentException("unknown type: " + type);
}
return supplier.get();
}
public void register(String type, Supplier<Handler> supplier) {
registry.put(type, supplier);
}
}
在上述代码中,EmailHandler::new 是构造方法引用,相当于一个 Supplier<Handler>。创建对象时不再走分支,而是根据 Key 从 Map 中取出函数并执行 get()。新增一个类型只调用注册方法并传入对应 Lambda 即可,不需要修改工厂主体,这符合开闭原则。
Map 的选择会直接影响并发场景下的表现。ConcurrentHashMap 可以提供安全的并发读写,但如果注册表只在启动阶段写入、运行期只读,使用 HashMap 也可以接受。为了防止使用方误改 Map,建议将其声明为 private final,只通过 register 和 create 暴露操作入口。
三、支持带参数创建与动态配置扩展
很多变量创建逻辑并不是无参的,例如数据库连接需要地址和账号,消息处理器可能需要读取密钥。此时可以把 Supplier 换成 Function,让 Lambda 接收一个上下文对象,再根据上下文生成实例。如下所示:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public class ContextHandlerFactory {
private final Map<String, Function<HandlerConfig, Handler>> registry = new ConcurrentHashMap<>();
public ContextHandlerFactory() {
registry.put("email", config -> new EmailHandler(config.getHost(), config.getPort()));
registry.put("sms", config -> new SmsHandler(config.getAccessKey(), config.getSecretKey()));
}
public Handler create(String type, HandlerConfig config) {
Function<HandlerConfig, Handler> function = registry.get(type);
if (function == null) {
throw new IllegalArgumentException("unknown type: " + type);
}
return function.apply(config);
}
}
引入 Function 后,工厂不再关心每个具体类的构造参数差异,这些差异被封装在各自 Lambda 的内部。配置模块可以从外部文件中读取参数并生成上下文,也可以直接把某个 Lambda 注册为 Bean。这样做让变量创建逻辑真正成为可以动态配置的资源。
插件化场景是这种设计的一大受益者。不同模块在初始化时调用注册方法,把自己负责的类型绑定到对应 Key 上。主程序不需要知道有哪些插件,只要维护好注册表即可。卸载插件时删除注册项,也不会影响其他模块。传统工厂要达到同样效果,往往需要引入反射或额外的配置文件解析体系。
四、缓存策略、异常处理与并发细节
Lambda 工厂默认每次调用都会创建新对象,因为 supplier.get() 会执行你提供的创建逻辑。如果某些类型适合复用同一个实例,可以在注册时对 Lambda 做一层缓存包装,例如借助 AtomicReference 实现延迟初始化。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public class CachedHandlerFactory {
private final Map<String, Supplier<Handler>> registry = new ConcurrentHashMap<>();
public void registerSingleton(String type, Supplier<Handler> supplier) {
AtomicReference<Handler> ref = new AtomicReference<>();
registry.put(type, () -> {
Handler handler = ref.get();
if (handler == null) {
handler = supplier.get();
ref.compareAndSet(null, handler);
return ref.get();
}
return handler;
});
}
public Handler create(String type) {
Supplier<Handler> supplier = registry.get(type);
if (supplier == null) {
throw new IllegalArgumentException("unknown type: " + type);
}
return supplier.get();
}
}
异常处理是函数式接口使用时的常见难题。Java 标准的 Supplier 和 Function 不允许直接抛出受检异常,如果创建过程可能失败,需要自定义一个支持异常的函数式接口,例如 CheckedSupplier<T>,在其抽象方法上声明 throws Exception。然后在工厂方法中捕获并包装为运行时异常或返回 Optional,避免异常直接穿透到调用方造成不友好的错误信息。
并发安全方面,使用 ConcurrentHashMap 能保证单个注册操作的原子性,但不代表组合操作一定线程安全。比如先判断 Key 是否存在再注册,可能被其他线程插入。应该在初始化阶段完成注册,运行期尽量减少修改。如果确实需要运行期动态调整,可以通过 putIfAbsent、replace 等原子方法控制覆盖逻辑。
整体来看,用 Lambda 表达式重构工厂模式,核心变化是把创建逻辑从控制流转换为 Map 中的函数对象。这样做降低了新类型的扩展成本,提升了可测试性和可配置性,同时保持了工厂调用方的简单接口。任何需要根据类型创建对象的场景,都可以优先考虑这种注册表加函数式接口的实现方式。