为什么使用接口 + 工厂?让一切都可替换的 Java 模式

发布: (2026年2月8日 GMT+8 05:39)
9 分钟阅读
原文: Dev.to

Source: Dev.to

Sergiy Yevtushenko

模式

每个组件——用例、处理步骤、适配器——都被定义为带有静态工厂方法的接口:

public interface ProcessOrder {
    record Request(String orderId, String paymentToken) {}
    record Response(OrderConfirmation confirmation) {}

    Result execute(Request request);

    interface ValidateInput {
        Result apply(Request raw);
    }
    interface ReserveInventory {
        Result apply(ValidRequest req);
    }
    interface ProcessPayment {
        Result apply(Reservation reservation);
    }
    interface ConfirmOrder {
        Result apply(Payment payment);
    }

    static ProcessOrder processOrder(
            ValidateInput validate,
            ReserveInventory reserve,
            ProcessPayment processPayment,
            ConfirmOrder confirm) {
        return request -> validate.apply(request)
                                  .flatMap(reserve::apply)
                                  .flatMap(processPayment::apply)
                                  .flatMap(confirm::apply);
    }
}

四个步骤。每个都是单方法接口。工厂方法接受所有依赖作为参数,并返回实现该用例的 lambda。方法体的阅读顺序正好像业务流程:validate → reserve → process payment → confirm

这并非随意的约定。该结构存在三个具体原因。

Reason 1: Substitutability Without Magic

Anyone can implement the interface—no framework, no inheritance hierarchy, no annotations.

Testing becomes trivial

@Test
void order_fails_when_inventory_insufficient() {
    var useCase = ProcessOrder.processOrder(
        request -> Result.success(new ValidRequest(request)), // always valid
        req -> INSUFFICIENT_INVENTORY.result(),             // always fails
        reservation -> { throw new AssertionError("unreachable"); },
        payment -> { throw new AssertionError("unreachable"); }
    );

    useCase.execute(new Request("order-1", "tok_123"))
           .onSuccess(Assertions::fail);
}

没有 mocking 框架,没有 @Mock 注解,也没有 when().thenReturn() 链。测试通过普通 lambda 构造出它所需要的精确场景。

Stubbing incomplete implementations during development

// Payment gateway isn’t ready yet? Stub it.
var useCase = ProcessOrder.processOrder(
    realValidator,
    realInventoryService,
    reservation -> Result.success(new Payment("stub-" + reservation.id(), Money.ZERO)),
    realConfirmation
);

负责库存的团队无需等待支付团队的实现。每一步都可以独立实现。

原因 2:实现隔离

每个实现都是自包含的。没有共享的基类,没有需要覆盖的抽象方法,实现之间根本没有耦合。

与典型抽象类方式的对比

// The abstract‑class trap
public abstract class AbstractOrderProcessor {
    protected final Logger log = LoggerFactory.getLogger(getClass());

    public final Result execute(Request request) {
        log.info("Processing order: {}", request.orderId());
        var result = doExecute(request);
        log.info("Order result: {}", result);
        return result;
    }

    protected abstract Result doExecute(Request request);
    protected abstract Result validate(Request request);

    // “Shared utility” that every subclass now depends on
    protected Result calculateTotal(List items) {
        // 47 lines of logic that one subclass needed once
    }
}

每个实现都会耦合到基类。修改 calculateTotal 需要了解所有子类。向 execute 添加日志会注入到每个实现中,无论是否合适。基类变成了一个引力井——累积共享代码,产生了本应毫不相干的实现之间的隐形依赖。

使用接口 + 工厂

没有共享的实现代码。就是这么简单。实现之间的每一次交叉都是不必要的耦合,带来相应的维护开销——甚至需要深入了解两个项目而不是一个,却没有任何好处。

// Implementation A: uses database
static ProcessPayment databasePayment(PaymentRepository repo) {
    return reservation -> repo.charge(reservation.paymentToken(),
                                      reservation.total())
                            .map(Payment::fromRecord);
}

// Implementation B: uses external API
static ProcessPayment stripePayment(StripeClient client) {
    return reservation -> client.createCharge(reservation.total(),
                                               reservation.paymentToken())
                            .map(Payment::fromStripe);
}

这些实现彼此互不知晓。它们不共享代码或基类。它们仅共享一个契约——接口,除此之外别无他物。

原因 3:一次性实现

细微之处在于:工厂方法返回的是一个 lambda(或本地记录)。它不能通过类名在外部引用,这鼓励 组合优于继承,并且可以轻松丢弃或替换实现,而不会影响公共 API。

// Example: a one‑off implementation used only in a specific test
var testUseCase = ProcessOrder.processOrder(
    validator,
    inventory,
    reservation -> Result.success(new Payment("test-" + reservation.id(),
                                               Money.of(0))),
    confirmer
);

因为实现仅以 lambda 形式存在,避免了意外的复用或隐藏的耦合。当需求变化时,只需提供不同的 lambda 或工厂方法即可。

TL;DR

  • 接口 + 工厂 为你提供:
    1. 纯粹的可替换性 – 任意实现都可以在不依赖框架的情况下互换。
    2. 隔离性 – 实现之间不共享隐藏的基类依赖。
    3. 一次性、可组合的实现 – 易于存根、测试和替换。

采用此模式可保持你的 Java 代码库模块化、可测试,并摆脱传统继承繁重设计中常见的“引力井”问题。

ProcessOrder – 函数式组合示例

static ProcessOrder processOrder(ValidateInput validate,
                                 ReserveInventory reserve,
                                 ProcessPayment processPayment,
                                 ConfirmOrder confirm) {
    return request -> validate.apply(request)          // 这个 lambda 是实现本身
                              .flatMap(reserve::apply)
                              .flatMap(processPayment::apply)
                              .flatMap(confirm::apply);
}

无直接实例化

没有代码写成 new ProcessOrderImpl()
没有其他代码依赖具体的实现类。
因为没有任何东西可以引用它,实现可以被完全替换。

  • 接口是设计产出。
  • 实现是偶然的。

为什么这很重要

你可能会觉得这只是学术性的讨论,直到你需要:

  • 用异步实现替换同步实现。
  • 将数据库适配器换成 API 适配器。
  • 在现有步骤周围添加缓存层。
  • 完全重写某一步的内部实现。

在每种情况下:

  1. 接口保持不变。
  2. 工厂方法的签名保持不变。
  3. 实现(没有任何引用)被替换掉。

结果:无需下游更改,无需适配层,也没有“向后兼容” 的麻烦。

复合效应

每个收益单独来看都很有价值,但它们共同构成了一个系统,使得:

测试即配置

组装你需要的真实组件和存根组件的精确组合。

  • 没有模拟框架的开销。
  • 没有 “全部模拟” 导致的测试脆弱性。

重构是安全的

替换实现不会破坏其他实现,因为它们不共享代码。

  • 编译器通过接口强制执行契约。

复杂度受限

理解一个实现只需要了解该实现本身以及它所使用的接口。

  • 没有深层类层次结构。
  • 没有耦合实现的共享工具。

增量开发自然进行

  • 对尚未准备好的部分使用存根。
  • 一次替换一个存根为真实实现。
  • 每一步都可以独立开发、测试和部署。

何时不适用?

当确实只有 一种实现且永远只有一种——例如纯工具函数、数学计算、简单的数据转换时。在这些情况下,静态方法完全合适。

只要 存在任何可能的多种实现(包括几乎总是存在的测试实现),该模式就能收回成本。

转变

大多数 Java 代码库默认使用具体类。接口的抽取往往在后期才进行,往往是被动的——要么因为测试的需求,要么因为出现了第二个实现。

翻转这种做法:

  1. 从接口开始。 首先定义契约。
  2. 稍后实现。 实现自然随之而来,而且当需要更改时,其他内容都不受影响。

接口是你设计的内容。
实现是你今天恰好写的代码。

0 浏览
Back to Blog

相关文章

阅读更多 »

Switch case 语句

概述 - switch case 是一种控制语句,可根据变量或表达式的值运行不同的代码块。 - 它通常更简洁……

Java中的类::

类定义:class 是用于创建 object 的蓝图或模板。它定义了 object 所拥有的 property、variable 和 behavior(method)。

Scrap Labs – 金属3D打印机

真实金属打印。由废料构建。 在Scrap Labs,我们的使命是通过最先进的金属打印技术赋能建设者和创作者,使其……