杰瑞科技汇

Java Web设计模式如何高效落地?

第一部分:道 - 核心思想与原则

在学习具体模式之前,必须先理解其背后的“道”,也就是设计原则,它们是设计模式的灵魂,是判断代码好坏的标尺。

Java Web设计模式如何高效落地?-图1
(图片来源网络,侵删)

SOLID 原则

这是面向对象设计的基石,尤其适用于构建复杂的 Web 应用。

  • S - 单一职责原则: 一个类应该只有一个引起它变化的原因,在 Web 开发中,这意味着 UserController 只应该处理 HTTP 请求和响应,而不应该同时包含业务逻辑和数据库访问逻辑。
  • O - 开闭原则: 软件实体应该对扩展开放,对修改关闭,这意味着当你需要增加新功能时(比如新的支付方式),应该通过添加新类来实现,而不是修改现有的、已经测试过的核心类。
  • L - 里氏替换原则: 子类必须能够替换其父类,这保证了继承体系的稳定性,是框架(如 Spring)依赖注入和 AOP 的基础。
  • I - 接口隔离原则: 客户端不应该依赖它不需要的接口,在 Web 层,这意味着你的 Service 接口应该尽量细化,而不是一个巨大的“上帝接口”。
  • D - 依赖倒置原则: 高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。这是 Spring 框架的核心思想,你的 Controller 依赖的是 UserService 接口,而不是 UserServiceImpl 具体实现,这样你就可以在不修改 Controller 的情况下,轻松地替换 UserServiceImpl

DRY (Don't Repeat Yourself) - 不要重复自己

任何一段知识,在你的系统中都应该有单一、明确、权威的表示,在 Web 开发中,重复的代码可能包括:

  • 重复的参数校验逻辑。
  • 重复的异常处理代码。
  • 重复的数据库操作模板代码。

KISS (Keep It Simple, Stupid) - 保持简单

选择最简单、最直接能解决问题的方案,避免过度设计,不要为了使用某个“高级”模式而使用它,简单、清晰的代码更容易维护和理解。


第二部分:术 - 经典设计模式在 Java Web 中的应用

Java Web 开发是一个典型的分层架构,设计模式在不同层中扮演着不同的角色。

Java Web设计模式如何高效落地?-图2
(图片来源网络,侵删)

表现层 - 处理 HTTP 请求与响应

a. MVC (Model-View-Controller) 模式

这是整个 Java Web 开发的骨架,几乎所有现代 Web 框架(Spring MVC, Struts, JSF)都基于此模式。

  • Model (模型): 业务数据和业务逻辑,通常是 Java 对象 (POJO/Entity) 和 Service 层。
  • View (视图): 负责展示数据,可以是 JSP, Thymeleaf, FreeMarker 模板,或者是现代的前端框架(React, Vue)返回的 JSON。
  • Controller (控制器): 接收用户请求,调用 Model 处理业务逻辑,然后选择合适的 View 返回响应,在 Spring 中,@Controller 注解的类。

之道: MVC 将关注点分离,使得代码结构清晰,职责明确,Controller 只是一个“交通警察”,不处理具体业务,只负责调度。

b. Front Controller (前端控制器) 模式

这是一个更高级的控制器模式,几乎所有现代 Web 框架的核心。

  • 作用: 所有请求都首先进入一个中心化的控制器(如 Spring 的 DispatcherServlet),它不处理业务逻辑,而是负责:
    • 请求分发:根据 URL 映射到具体的 Handler (Controller)。
    • 公共逻辑处理:如日志记录、权限认证、请求/响应过滤等。
  • 实现: Spring MVC 的 DispatcherServlet 是该模式的完美体现。
  • 之道: 提供了统一的请求入口,实现了请求处理的“管道化”,便于添加横切关注点(如 AOP)。

c. Command (命令) 模式

常用于处理表单提交或 API 请求。

  • 作用: 将一个请求封装为一个对象(Command 对象),从而允许你使用不同的请求对客户端进行参数化。
  • 实现: Spring MVC 中的 @ModelAttribute 或直接将表单数据绑定到一个 POJO,这个 POJO 就扮演了 Command 对象的角色,Controller 接收这个 Command 对象并执行操作。
  • 之道: 将请求的数据和操作行为解耦,使得代码更清晰,易于测试。

d. View Helper (视图助手) 模式

在前后端分离不彻底的时代(如 JSP + Servlet),非常有用。

  • 作用: 在 JSP 等视图技术中,将复杂的业务逻辑或数据格式化逻辑从视图中抽离出来,交给专门的 Helper 类处理。
  • 实现: 创建一个 JavaBean,在 JSP 中通过 <jsp:useBean><jsp:getProperty> 来调用它的方法,生成复杂的 HTML 片段。
  • 之道: 保持视图的纯粹性,避免在 JSP 中出现大量的 Java 代码(Scriptlet),使视图更易于维护。

业务逻辑层 - 处理核心业务

a. Business Delegate (业务代表) 模式

用于解耦表现层和业务层,尤其在 EJB 时代非常流行,现在在微服务架构中仍有价值。

  • 作用: 在 Controller 和 Service 之间增加一个代理层,Controller 不直接调用 Service,而是调用 Business Delegate,Delegate 负责查找和调用实际的 Service。

  • 实现:

    // Controller
    @Autowired
    private BusinessDelegate businessDelegate;
    @GetMapping("/order")
    public String getOrder() {
        // Controller 只和 Delegate 打交道,不知道具体的 OrderService
        Order order = businessDelegate.getOrderById(123L);
        return "orderView";
    }
    // Business Delegate
    @Service
    public class BusinessDelegate {
        @Autowired
        private OrderService orderService; // 实际的业务服务
        public Order getOrderById(Long id) {
            // 可以在这里添加缓存、日志等
            return orderService.findById(id);
        }
    }
  • 之道: 提供了一层抽象,可以隐藏后端服务的复杂性,比如服务查找、负载均衡、异常处理等,使得 Controller 非常“干净”。

b. Service Locator (服务定位器) 模式

与 Business Delegate 类似,但关注点不同。

  • 作用: 提供一个全局的、中心化的服务查找机制,客户端(如 Controller)通过 Service Locator 来获取服务实例,而不是直接依赖注入。
  • 实现: Spring 的依赖注入容器本身就是一个巨大的“服务定位器”,虽然我们通常推荐使用 @Autowired,但 ApplicationContextAwaregetBean() 的方式就是 Service Locator 模式的体现。
  • 之道: 提供了灵活的服务获取方式,但会引入对容器的依赖,降低了代码的可测试性,在 Spring 中,优先使用依赖注入。

c. Transaction Script (事务脚本) 模式

最直接的业务逻辑组织方式。

  • 作用: 每一个方法对应一个业务过程(或用例),方法内部按步骤调用数据访问层,完成业务操作。

  • 实现: Service 层的方法就是一个个的“脚本”。

    @Service
    public class UserService {
        @Autowired
        private UserRepository userRepository;
        public void registerUser(UserRegistrationDto dto) {
            // 1. 检查用户名是否已存在
            if (userRepository.existsByUsername(dto.getUsername())) {
                throw new UserAlreadyExistsException();
            }
            // 2. 创建新用户
            User user = new User(dto.getUsername(), dto.getPassword());
            // 3. 保存用户
            userRepository.save(user);
        }
    }
  • 之道: 简单、直接,易于理解和实现,适合业务逻辑相对简单的应用,但如果业务变复杂,脚本会变得很长,难以维护。

d. Domain Model (领域模型) 模式

与 Transaction Script 相对,是处理复杂业务逻辑的利器。

  • 作用: 将业务逻辑和数据封装在对象模型中,对象不仅包含数据,还包含操作这些数据的行为(方法)。

  • 实现: 这是 DDD(领域驱动设计)的核心,一个 Order 对象可以有一个 calculateTotal() 方法,而不是在 Service 层计算总价。

    public class Order {
        private List<OrderItem> items;
        public BigDecimal calculateTotal() {
            return items.stream()
                       .map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
                       .reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        public void addItem(OrderItem item) {
            // 可以在这里添加业务规则,比如不能添加重复的商品
            this.items.add(item);
        }
    }
  • 之道: 将业务逻辑内聚到最相关的对象中,使得代码更具表达力,更贴近业务语言,非常适合复杂的业务领域。

数据访问层 - 与数据库交互

a. Data Access Object (DAO) 模式

数据访问层的标准模式

  • 作用: 将低级的数据访问 API(如 JDBC, JPA)与高级的业务服务分离开,提供一个抽象的接口来操作数据库,具体的实现(如 JDBC 实现, JPA 实现)则隐藏在接口后面。

  • 实现:

    // Repository 接口本质上是 DAO 的现代变种
    public interface UserRepository {
        User findById(Long id);
        void save(User user);
    }
    // JPA 实现
    @Repository
    public class JpaUserRepository implements UserRepository {
        @Autowired
        private EntityManager entityManager;
        @Override
        public User findById(Long id) {
            return entityManager.find(User.class, id);
        }
        // ...
    }
  • 之道: 实现了数据访问层与业务逻辑层的解耦,你可以轻松地更换数据访问技术(比如从 JPA 换成 MyBatis),而无需改动 Service 层的任何代码。

b. Active Record (活动记录) 模式

一种更简单的数据映射模式。

  • 作用: 对象本身就包含数据访问的逻辑,对象既包含数据字段,也包含 CRUD(增删改查)方法,对象与数据库表一一对应。
  • 实现: Ruby on Rails 的 ActiveRecord 是其典型代表,在 Java 中,一些轻量级框架或特定场景下也会使用,但不如 DAO 普遍。
  • 之道: 代码非常简洁,适合简单的 CRUD 应用,但缺点是对象模型与数据访问逻辑耦合,当业务变复杂时,会导致模型变得臃肿。

其他重要模式

a. Singleton (单例) 模式

  • 应用: Spring 框架中,默认情况下,所有 @Service, @Repository, @Component 都是单例的,这确保了整个应用中只有一个共享的 Bean 实例,节省了内存。
  • 之道: 用于管理共享资源,但要注意线程安全问题,Spring 的单例默认是线程安全的(Bean 是无状态的或有状态但方法同步得当)。

b. Factory / Abstract Factory (工厂/抽象工厂) 模式

  • 应用: Spring 本身就是一个巨大的工厂(BeanFactory),当你使用 new 关键字创建对象时,是硬编码依赖,而通过 Spring 的依赖注入,你实际上是在使用工厂模式来创建和管理对象。
  • 实现: Spring Boot 的自动配置机制也大量使用了工厂模式,根据类路径下的依赖自动创建和配置 Bean。
  • 之道: 将对象的创建和使用分离,降低了模块间的耦合度,提高了系统的灵活性和可扩展性。

c. Strategy (策略) 模式

  • 应用: 处理“同一行为,不同算法”的场景。
    • 支付方式: 定义一个 PaymentStrategy 接口,AlipayStrategy, WechatPayStrategy, CreditCardStrategy 分别实现它,在订单结算时,根据用户选择调用不同的策略。
    • 优惠券计算: 定义一个 DiscountStrategy 接口,FullReductionStrategy, PercentageStrategy 等。
  • 实现: Spring 的 @Profile@Conditional 注解可以看作是策略模式的一种应用,根据不同的环境条件选择不同的 Bean。
  • 之道: 将算法族封装起来,使它们可以互相替换,让算法的变化独立于使用它的客户端。

d. Decorator (装饰器) 模式

  • 应用: 动态地给一个对象添加一些额外的职责。
    • IO 流: InputStream 是一个抽象组件,BufferedInputStream 是一个装饰器,为 InputStream 添加了缓冲功能。
    • Spring Security: 拦截器链可以看作是装饰器模式的应用,对请求进行层层包装和增强。
  • 之道: 提供了比继承更灵活的扩展对象功能的方式,可以动态地组合功能。

第三部分:器 - 框架与工具中的模式

现代 Java Web 开发离不开框架,而框架本身就是设计模式的集大成者。

  • Spring Framework:

    • IoC/DI: 实现了依赖倒置原则,是整个框架的基石。
    • AOP (面向切面编程): 实现了代理模式(JDK 动态代理、CGLIB),用于横切关注点(如事务、日志、安全)。
    • MVC: 实现了前端控制器和 MVC 模式。
    • Template Method: JdbcTemplate, RestTemplate 等是模板方法模式的体现,定义了算法骨架,由子类实现具体步骤。
    • Factory: BeanFactory 和 ApplicationContext 是工厂模式的实现。
  • Spring Boot:

    • Auto-Configuration: 大量使用了策略模式和条件注解,根据类路径和环境自动配置 Bean。
    • Starter: 是一种构建模式,将一组相关的依赖打包,简化了配置。

第四部分:实践之道 - 如何选择与应用

  1. 从原则出发,而非模式: 先想清楚 SOLID 原则,再考虑是否需要一个模式来解决问题,不要为了模式而模式。
  2. 保持简单: KISS 原则第一,如果用几行简单代码就能解决,就不要引入复杂的模式。
  3. 演进式设计: 不要试图在设计阶段就规划好所有模式,随着业务复杂度的增加,当现有代码变得难以维护时,再考虑引入合适的设计模式进行重构。
  4. 模式是工具,不是银弹: 没有万能的模式,每个模式都有其适用场景和代价(如增加复杂性),过度使用策略模式会导致类数量激增。
  5. 理解模式本质: 不要只记模式的名称和结构,要理解它要解决什么问题,它的优缺点是什么,这样你才能在合适的场景下正确地使用它。
  6. 重构是关键: 将设计模式应用到现有代码中,最好的方式是通过重构,当你发现一段代码违反了某个原则(如 SRP),就可以考虑用相应的设计模式(如 Command 模式)来重构它。

Java Web 设计模式之道,是一个从“道”(原则) -> “术”(模式) -> “器”(框架) -> “行”(实践)的完整闭环。

  • 是 SOLID 等原则,是你判断代码好坏的内在标尺。
  • 是 MVC、DAO、Strategy 等经典模式,是你解决具体问题的工具箱。
  • 是 Spring 等框架,它们已经为你封装了大量的模式,让你能站在巨人的肩膀上。
  • 是在实践中不断思考、选择、应用和重构,最终形成你自己的设计哲学。

最终的目标不是成为一个“设计模式收藏家”,而是成为一个能用最恰当的方式,写出优雅、健壮代码的工程师

分享:
扫描分享到社交APP
上一篇
下一篇