以分页场景谈MVC设计模式

18 篇文章 0 订阅

一 、需求场景

在这里插入图片描述
需要实现一个分页组件, 可以方便的进行分页操作。

二、分析需求

从分页需求出发,分析潜在的元素, 虽然只包含一个大的分页功能,但是潜在的元素
包含:上一页 下一页 首页 尾页 当前页 等等。

为什么包含这些元素 ?==> 理解业务的能力

梳理元素之间的关系

上一页 == 当前页左移
下一页 == 当前页右移
首页 == 当前页左移到第一个
尾页 == 当前页右移动到最后一个
切换页码== 重新建立一个上一页、下一页、首页、尾页、当前页之间的关系逻辑

三、代码设计

1. 使用MVC模式梳理

为什么选择MVC模式
为什么不上来就干 (基本的从上至下,从左至右的标准规则)
可不可以选择其他设计模式

1.1 MVC简介

MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
在这里插入图片描述

1.2 分析和创建模型

抽象模型是最难的事在于:

  1. 命名
    这个类要不要作为基类,使用场景多不多,起什么样的名字更贴合意义呢?
  2. 属性建立
    在这个业务场景中的属性够不够,增加属性会不会打破属性间的逻辑关系。

2 建立分页model

基于需求中的元素,建立模型
在这里插入图片描述

  • setCurrentPage 公共的可以改变模型中属性的方法
    第一个参数改变当前页的大小,第二个参数改变每页的大小 ,随着当前页和每页大小的改变,随之触发上一页 、下一页、首页、尾页的变化
import lombok.Getter;
import lombok.ToString;

import java.io.Serializable;

/**
 * 分页对象类
 */
@Getter
@ToString
public class Pager implements Serializable {

    private static final long serialVersionUID = 4542617637761955078L;
    /**
     * 当前页
     */
    private int currentPage;
    /**
     * 每页大小
     */
    private int pageSize;
    /**
     * 总页数
     */
    private int pageTotal;
    /**
     * 总条数
     */
    private int totalCount;
    /**
     * 前一页
     */
    private int prevPage;
    /**
     * 下一页
     */
    private int nextPage;
    /**
     * 第一页
     */
    private int firstPage;
    /**
     * 最后一页
     */
    private int lastPage;

    public Pager(int totalCount) {
        this(totalCount, 1, 10);
    }

    public Pager(int totalCount, int currentPage, int pageSize) {
        this.totalCount = totalCount;
        this.firstPage = 1;
        this.setCurrentPage(currentPage, pageSize);
    }

    public void setCurrentPage(int currentPage) {
        this.setCurrentPage(currentPage, this.pageSize);
    }

    /**
     * 设置当前页
     *
     * @param currentPage 当前页
     * @param pageSize    每页数量
     */
    public void setCurrentPage(int currentPage, int pageSize) {
        this.currentPage = currentPage;
        if (this.pageSize != pageSize) {
            this.pageSize = pageSize;
            this.pageTotal = this.totalCount % this.pageSize > 0 ? this.totalCount / this.pageSize + 1 : this.totalCount / this.pageSize;
            this.lastPage = this.pageTotal;
        }

        if (this.currentPage > this.pageTotal) {
            this.currentPage = this.pageTotal;
        }
        if (currentPage > 1) {
            this.prevPage = this.currentPage - 1;
        } else {
            this.prevPage = this.firstPage;
        }
        if (this.currentPage < this.lastPage) {
            this.nextPage = this.currentPage + 1;
        } else {
            this.nextPage = this.lastPage;
        }
    }


}

3. 建立view和control

视图层:
包含了元素的布局、样式 和基本的动作事件及约束
控制层:
包含了 view和视图之间的逻辑关系, 所以最核心的方法currentPageChange
随着当前页和分页大小的变化,控制model和view的变化
在这里插入图片描述

3.1 控制view

所有的view的元素事件都与该方法绑定,根据model的变化,更改view中上一页、下一页 、首页、尾页最大页等元素的状态变化

3.2 控制model

将currentPage当前页和pageSize分页大小的变化传递给model,自动控制上一页、下一页、尾页和首页的属性变化


    private void currentPageChange(int currentPage, int pageSize) {
        pager.setCurrentPage(currentPage, pageSize);
        limitNumberDocument.setMax(pager.getLastPage());
        totalPage.setText("/" + pager.getPageTotal());

        jump.setText(pager.getCurrentPage() + "");
        if (currentPage <= pager.getFirstPage()) {
            first.setEnabled(false);
            prev.setEnabled(false);
            next.setEnabled(true);
            last.setEnabled(true);
        } else if (currentPage >= pager.getLastPage()) {
            first.setEnabled(true);
            prev.setEnabled(true);
            next.setEnabled(false);
            last.setEnabled(false);
        } else {
            first.setEnabled(true);
            prev.setEnabled(true);
            next.setEnabled(true);
            last.setEnabled(true);
        }


    }

3.3 完整示例
import cn.hutool.core.util.StrUtil;
import cn.note.swing.core.document.LimitNumberDocument;
import cn.note.swing.core.event.ConsumerAction;
import cn.note.swing.core.event.key.KeyActionFactory;
import cn.note.swing.core.util.FrameUtil;
import cn.note.swing.core.view.AbstractMigView;
import cn.note.swing.core.view.base.SelectedComboBox;
import cn.note.swing.core.view.form.SelectedItem;
import cn.note.swing.core.view.theme.ThemeFlatLaf;
import net.miginfocom.swing.MigLayout;

import javax.swing.*;

/**
 * 分页测试
 */
public class PaginationTest extends AbstractMigView {

    /* 分页模型*/
    private Pager pager;

    /* 第一页*/
    private JButton first;

    /*上一页*/
    private JButton prev;

    /* 下一页*/
    private JButton next;

    /* 最大页数 */
    private JButton last;

    /* 总页数 */
    private JLabel totalPage;

    /* 回车跳转*/
    private JTextField jump;

    /* 下拉选择*/
    private SelectedComboBox selectItems;

    /* 最大数限制*/
    private LimitNumberDocument limitNumberDocument;

    @Override
    protected MigLayout defineMigLayout() {
        return new MigLayout("");
    }

    @Override
    protected void render() {
        pager = new Pager(100);
        first = new JButton("《");
        prev = new JButton("<");
        next = new JButton(">");
        last = new JButton("》");
        totalPage = new JLabel("/" + pager.getPageTotal());
        jump = new JTextField();
        // 限制最大输入页数
        limitNumberDocument = new LimitNumberDocument(pager.getLastPage());
        jump.setDocument(limitNumberDocument);
        
        // 条数
        selectItems = new SelectedComboBox();
        selectItems.addSelectItem(new SelectedItem("10", "10 条/页"));
        selectItems.addSelectItem(new SelectedItem("20", "20 条/页"));
        selectItems.addSelectItem(new SelectedItem("30", "30 条/页"));
        selectItems.addSelectItem(new SelectedItem("40", "40 条/页"));
        selectItems.addSelectItem(new SelectedItem("50", "50 条/页"));


        view.add(first);
        view.add(prev);
        view.add(jump);
        view.add(totalPage);
        view.add(next);
        view.add(last);
        view.add(selectItems);

        handleActions();
        currentPageChange(1, 10);
    }


    /**
     * 拦截所有动作
     */
    private void handleActions() {
        // 第一页 上一页 下一页 最后一页
        first.addActionListener(e -> currentPageChange(pager.getFirstPage()));
        prev.addActionListener(e -> currentPageChange(pager.getPrevPage()));
        next.addActionListener(e -> currentPageChange(pager.getNextPage()));
        last.addActionListener(e -> currentPageChange(pager.getLastPage()));


        // 回车限制输入不合法时,重置为1
        KeyActionFactory.bindEnterAction(jump, new ConsumerAction(e -> {
            String text = jump.getText();
            if (StrUtil.isBlank(text) || Integer.valueOf(text) == 0) {
                jump.setText(String.valueOf(pager.getFirstPage()));
            }
            currentPageChange(Integer.valueOf(jump.getText()));
        }));


        // 变换条数
        selectItems.onSelected(item -> {
            currentPageChange(pager.getCurrentPage(), Integer.valueOf(item.getKey()));
        });
    }


    /**
     * 当前页变化
     *
     * @param currentPage 当前页
     */
    private void currentPageChange(int currentPage) {
        currentPageChange(currentPage, pager.getPageSize());
    }

    /**
     * 当前页变化事件
     *
     * @param currentPage 当前页
     * @param pageSize    分页大小
     */
    private void currentPageChange(int currentPage, int pageSize) {
        pager.setCurrentPage(currentPage, pageSize);
        limitNumberDocument.setMax(pager.getLastPage());
        totalPage.setText("/" + pager.getPageTotal());

        jump.setText(pager.getCurrentPage() + "");
        if (currentPage <= pager.getFirstPage()) {
            first.setEnabled(false);
            prev.setEnabled(false);
            next.setEnabled(true);
            last.setEnabled(true);
        } else if (currentPage >= pager.getLastPage()) {
            first.setEnabled(true);
            prev.setEnabled(true);
            next.setEnabled(false);
            last.setEnabled(false);
        } else {
            first.setEnabled(true);
            prev.setEnabled(true);
            next.setEnabled(true);
            last.setEnabled(true);
        }


    }

    public static void main(String[] args) {
        ThemeFlatLaf.install();
        FrameUtil.launchTime(PaginationTest.class);
    }
}

4. 包装为组件及 美化

4.1 使用图标及样式代码进行美化

在这里插入图片描述

4.2 使用抽象进行组件封装加入与外部交互
  1. 添加Consumer 进行pager变换的传递
    在这里插入图片描述
    2. 在currentChange中 触发该方法
    在这里插入图片描述

5. 实战与表格分页

当你的表格需要分页时, 那么你就可以把你包装的分页组件加进来

  • 代码是轻松维护的, 表格逻辑和分页逻辑无关
  • 代码是可扩展的, 改变分页的风格不会影响表格代码, 因为表格只绑定了分页的model

MVC的设计模式, 没有增加代码量 , 但是让代码具备低耦合和高扩展的特性

在这里插入图片描述
请添加图片描述

四、 语言的互通

1. MVC 在JavaScript中适配

将上述场景,使用MVC还原在h5中
在这里插入图片描述

使用类构建js代码容易维护吗?

2. 完整示例

<html>
<body>
  <div>
    <button id="first"></button>
    <button id="prev"></button>
    <!-- 限制只能输入数字 -->
    <input type="text" id="jump" onkeyup="this.value=this.value.replace(/[^\d]/g,'') "
      onafterpaste="this.value=this.value.replace(/[^\d]/g,'') ">
    <label id="totalPage"></label>
    <button id="next"></button>
    <button id="last"></button>
    <select id="pageSizeSelect">
      <option value="10">10 条/页</option>
      <option value="20">20 条/页</option>
      <option value="30">30 条/页</option>
      <option value="40">40 条/页</option>
      <option value="50">50 条/页</option>
    </select>
  </div>
</body>
<script>
  class Pager {
    /*当前页*/
    currentPage;
    /*每页大小*/
    pageSize;
    /*总页数*/
    totalPage;
    /*总条数*/
    totalCount;
    /*前一页*/
    prevPage;
    /*下一页*/
    nextPage;
    /*第一页*/
    firstPage;
    /*最后一页*/
    lastPage;

    constructor(totalCount, currentPage = 1, pageSize = 10) {
      this.totalCount = totalCount;
      this.firstPage = 1;
      this.setCurrentPage(currentPage, pageSize);
    }

    /**
     * 设置当前页
     *
     * @param currentPage 当前页
     * @param pageSize    每页数量
     */
    setCurrentPage(currentPage, pageSize) {
      this.currentPage = Number(currentPage);
      if (this.pageSize != pageSize) {
        this.pageSize = pageSize;
        this.totalPage = this.totalCount % this.pageSize > 0 ? Math.floor(this.totalCount / this.pageSize) + 1 : Math.floor(this.totalCount / this.pageSize);
        this.lastPage = this.totalPage;
      }

      if (this.currentPage > this.totalPage) {
        this.currentPage = this.totalPage;
      }
      if (this.currentPage > 1) {
        this.prevPage = this.currentPage - 1;
      } else {
        this.prevPage = this.firstPage;
      }
      if (this.currentPage < this.lastPage) {
        this.nextPage = this.currentPage + 1;
      } else {
        this.nextPage = this.lastPage;
      }
    }
  }


  class PaginationTest {

    /* 分页模型*/
    pager;
    /* 第一页*/
    first;
    /*上一页*/
    prev;
    /* 下一页*/
    next;
    /* 最大页数 */
    last;
    /* 总页数 */
    totalPage;
    /* 回车跳转*/
    jump;
    /* 下拉选择*/
    pageSizeSelect;

    constructor(totalCount) {
      this.pager = new Pager(totalCount);
      this.first = document.getElementById("first");
      this.prev = document.getElementById("prev");
      this.next = document.getElementById("next");
      this.last = document.getElementById("last");
      this.totalPage = document.getElementById("totalPage");
      this.jump = document.getElementById("jump");
      this.pageSizeSelect = document.getElementById("pageSizeSelect");

      this.handleActions();
      this.currentPageChange(1, 10);
    }
    /**
     * 拦截所有动作
     */
    handleActions() {
      this.first.onclick = () => { this.currentPageChange(this.pager.firstPage, this.pager.pageSize) }
      this.prev.onclick = () => { this.currentPageChange(this.pager.prevPage, this.pager.pageSize) }
      this.next.onclick = () => { this.currentPageChange(this.pager.nextPage, this.pager.pageSize) }
      this.last.onclick = () => { this.currentPageChange(this.pager.lastPage, this.pager.pageSize) }
      this.pageSizeSelect.onchange = (e) => {
        this.currentPageChange(this.pager.currentPage, e.target.value)
        if (this.jump.value > this.pager.totalPage) {
          this.jump.value = this.pager.totalPage
        }
      }

      // 限制最大输入
      this.jump.oninput = () => {
        let v = this.jump.value;
        if (v > this.pager.totalPage) {
          this.jump.value = this.pager.totalPage
        }
      }

      this.jump.onchange = () => {
        let v = this.jump.value;
        if (!v) {
          this.jump.value = 1;
        }
        this.currentPageChange(this.jump.value, this.pager.pageSize)
      }
    }

    /**
     * 当前页变化事件
     *
     * @param currentPage 当前页
     * @param pageSize    分页大小
     */
    currentPageChange(currentPage, pageSize = 10) {
      this.pager.setCurrentPage(currentPage, pageSize);
      this.jump.value = currentPage;
      this.totalPage.innerHTML = "/" + this.pager.totalPage;

      if (currentPage <= this.pager.firstPage) {
        this.first.disabled = true;
        this.prev.disabled = true;
        this.next.disabled = false;
        this.last.disabled = false;
      } else if (currentPage >= this.pager.lastPage) {
        this.first.disabled = false;
        this.prev.disabled = false;
        this.next.disabled = true;
        this.last.disabled = true;
      } else {
        this.first.disabled = false;
        this.prev.disabled = false;
        this.next.disabled = false;
        this.last.disabled = false;
      }
    }
  }
  new PaginationTest(100);
</script>
</html>

五、后话

  • 要不要建立模型 ,你给抽通用模型的时间了,催催催?
  • 抽象完的代码能坚持多久,这项目不知道哪天就黄了,还有复用的可能 ?
  • 封装为组件时,控制层需要释放哪些属性 ,那些user天天要我改组件怎么办?
  • 业务代码和非业务代码 使用不使用MVC模式的重点在于,需求变化的可控性 (至上)和程序员的一点骄傲(唯心)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值