当前位置: 首页 > article >正文

【设计模式系列】组合模式(十二)

目录

一、什么是组合模式

二、组合模式的角色

三、组合模式的典型应用

四、组合模式在Mybatis SqlNode中的应用

4.1 XML映射文件案例

4.2 Java代码使用案例


一、什么是组合模式

组合模式(Composite Pattern)是一种结构型设计模式,其核心思想是将对象组合成树状结构,使得单个对象和对象的组合能够以相同的方式被处理。这种模式提供了一个将对象表示为部分-整体层次结构的方法,允许客户端对单个对象和组合对象的使用具有一致性。

二、组合模式的角色

  1. Component(抽象构件)

    • 作用:定义了对象结构的公共接口,包括业务方法和在需要时访问和管理其子构件的方法(如addremovegetChild)。这个接口可以是抽象类或接口。
    • 细节:Component作为组合中的对象的公共类或接口,使得叶子构件和组合构件可以被统一对待。
  2. Leaf(叶子构件)

    • 作用:表示对象结构中的叶节点,没有子构件。叶子构件实现了抽象构件定义的接口,但是其add或remove操作通常不做任何事情(可能是抛出异常或者简单返回)。
    • 细节:Leaf对象通常包含实现细节,因为它们不包含子构件,所以它们是实际执行业务逻辑的末端对象。
  3. Composite(组合构件)

    • 作用:表示对象结构中的复合节点,它可以包含子构件。Composite对象存储子构件集合,并实现在抽象构件中定义的方法来管理子构件。
    • 细节:Composite对象实现了添加和删除子构件的方法,并且通常会递归地调用其子构件的业务方法,以确保整个结构的一致性。

三、组合模式的典型应用

  1. 构建树形结构:任何需要表示部分-整体层次结构的场景,如组织架构、类目体系等。

  2. 创建复杂对象:在需要构建复杂对象,而这些对象由更简单的对象组成时,如构建一个由多个部件组成的汽车对象。

  3. 处理递归结构:当需要处理递归结构的数据时,如遍历、搜索、排序等操作。

  4. 实现插件架构:在需要构建一个可扩展的插件架构时,可以使用组合模式来表示插件的层次结构。

四、组合模式在Mybatis SqlNode中的应用

4.1 XML映射文件案例

  1. 动态SQL构建:MyBatis的动态SQL功能通过<if><choose><when><otherwise><trim><where><set><foreach>等标签,组合成非常灵活的SQL语句,提高开发人员的效率。

  2. SqlNode接口SqlNode接口是MyBatis中用于存储SQL的节点,它有一个apply抽象方法,用于将SQL节点应用到动态上下文中。

以下是SqlNode接口及其两个实现类MixedSqlNodeIfSqlNode的简单示例:

public interface SqlNode {
    boolean apply(DynamicContext context);
}

public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    @Override
    public boolean apply(DynamicContext context) {
        contents.forEach(node -> node.apply(context));
        return true;
    }
}

public class IfSqlNode implements SqlNode {
    private ExpressionEvaluator evaluator;
    private String test;
    private SqlNode contents;

    public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
    }

    @Override
    public boolean apply(DynamicContext context) {
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false;
    }
}

使用案例

假设我们有一个用户表(users),包含字段idnameemailstatus。我们需要根据不同的条件动态生成查询SQL。

<select id="selectUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    <if test="emails != null and emails.size > 0">
        AND email IN
        <foreach item="email" collection="emails" open="(" separator="," close=")">
            #{email}
        </foreach>
    </if>
</select>

在这个例子中,<where>标签内部包含了两个<if>标签,这两个<if>标签对应的SqlNode会被MixedSqlNode组合在一起进行处理。如果namestatus不为空,相应的条件会被添加到SQL中。如果emails列表不为空,<foreach>标签会生成一个IN条件子句,其中每个邮箱地址都会被包含在列表中。

通过这种方式,MyBatis能够根据传入的参数动态地构建SQL语句,使得SQL语句的构建更加灵活和强大。这种模式的应用提高了MyBatis动态SQL的灵活性和可扩展性。

4.2 Java代码使用案例

1. 定义SqlNode接口和实现类

首先,定义SqlNode接口和一些实现类,包括TextSqlNodeIfSqlNodeForEachSqlNode

public interface SqlNode {
    boolean apply(DynamicContext context);
}

public class TextSqlNode implements SqlNode {
    private String text;

    public TextSqlNode(String text) {
        this.text = text;
    }

    @Override
    public boolean apply(DynamicContext context) {
        context.appendText(text);
        return true;
    }
}

public class IfSqlNode implements SqlNode {
    private String condition;
    private SqlNode ifTrue;

    public IfSqlNode(String condition, SqlNode ifTrue) {
        this.condition = condition;
        this.ifTrue = ifTrue;
    }

    @Override
    public boolean apply(DynamicContext context) {
        if (Boolean.parseBoolean(context.getBindings().getOrDefault(condition, "false").toString())) {
            return ifTrue.apply(context);
        }
        return false;
    }
}

public class ForEachSqlNode implements SqlNode {
    private String collection;
    private String item;
    private String open;
    private String close;
    private String separator;
    private SqlNode contents;

    public ForEachSqlNode(String collection, String item, String open, String close, String separator, SqlNode contents) {
        this.collection = collection;
        this.item = item;
        this.open = open;
        this.close = close;
        this.separator = separator;
        this.contents = contents;
    }

    @Override
    public boolean apply(DynamicContext context) {
        List<String> emails = (List<String>) context.getBindings().get(collection);
        if (emails != null && !emails.isEmpty()) {
            context.appendText(open);
            for (int i = 0; i < emails.size(); i++) {
                context.getBindings().put(item, emails.get(i));
                contents.apply(context);
                if (i < emails.size() - 1) {
                    context.appendText(separator);
                }
            }
            context.appendText(close);
        }
        return true;
    }
}

2. 创建DynamicContext类

DynamicContext类用于存储和传递动态SQL生成过程中的上下文信息:

import java.util.HashMap;
import java.util.Map;

public class DynamicContext {
    private StringBuilder sql = new StringBuilder();
    private Map<String, Object> bindings = new HashMap<>();

    public void appendText(String text) {
        sql.append(text);
    }

    public String getSql() {
        return sql.toString();
    }

    public Map<String, Object> getBindings() {
        return bindings;
    }

    public void setBindings(Map<String, Object> bindings) {
        this.bindings = bindings;
    }
}

3. 使用SqlNode构建和执行动态SQL

在Java代码中使用SqlNode构建和执行动态SQL:

public class Main {
    public static void main(String[] args) {
        DynamicContext context = new DynamicContext();
        Map<String, Object> params = new HashMap<>();
        params.put("name", "John Doe");
        params.put("status", "ACTIVE");
        params.put("emails", List.of("john.doe@example.com", "jane.doe@example.com"));
        context.setBindings(params);

        SqlNode rootNode = new TextSqlNode("SELECT * FROM users WHERE ");
        rootNode.apply(context);

        new IfSqlNode("name != null",
            new TextSqlNode("AND name = #{name}")).apply(context);

        new IfSqlNode("status != null",
            new TextSqlNode("AND status = #{status}")).apply(context);

        new IfSqlNode("emails != null && !emails.isEmpty()",
            new ForEachSqlNode("emails", "email", "(", ")", ",",
                new TextSqlNode("AND email = #{email}"))).apply(context);

        System.out.println("Generated SQL: " + context.getSql());
    }
}

在这个示例中,DynamicContext类用于存储和传递动态SQL生成过程中的上下文信息。Main类展示了如何使用SqlNode构建和执行动态SQL。IfSqlNodeTextSqlNode用于条件判断和添加文本,而ForEachSqlNode用于处理集合类型的参数,模拟MyBatis中的<foreach>标签。

这个示例展示了如何在Java代码中模拟MyBatis的动态SQL行为,包括使用ForEachSqlNode来处理集合类型的参数。


http://www.kler.cn/a/378644.html

相关文章:

  • Java复习35(PTA)
  • Spring框架的事务管理
  • torchvision.io.write_video 报错替换
  • python 五子棋小游戏
  • 证书下行,这些高质量IT证书仍值得考
  • 【MyBatis】【基于轻量型架构的WEB开发】课程 课后习题 章节测试
  • 【系统设计】提升Kafka系统性能:Spring Boot实现Lag感知的生产者与消费者
  • HTML 基础标签——分组标签 <div>、<span> 和基础语义容器
  • 【设计模式】结构型模式(二):代理模式
  • 【Axure视频教程】中继器控制显示不同内容
  • 学习笔记——三小时玩转JQuery
  • 安利一款开源企业级的报表系统SpringReport
  • 关于游戏加加不可以在cs2中显示的解决方案
  • Harmony OS搭建广告展示页
  • FastAPI 从0到1(中间件和跨域篇)筛选
  • 上海亚商投顾:沪指缩量调整 华为概念股午后爆发
  • C++算法练习-day31——二叉树的前/中/后序遍历
  • CentOS系统查看CPU、内存、操作系统等信息
  • 第三百零一节 Lucene教程 - Lucene索引文件
  • 开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序中积分使用价值的拓展策略
  • 汽车车牌校验
  • [linux]docker快速入门
  • 华为OD机试 - 连续天数的最高利润额 - 动态规划(Python/JS/C/C++ 2024 C卷 100分)
  • MySQL数据库的使用
  • C#结合JS解决Word添加无效位图导致进程停滞的问题
  • Mybatis Plus 自定义 SQL