一 、需求场景
需要实现一个分页组件, 可以方便的进行分页操作。
二、分析需求
从分页需求出发,分析潜在的元素, 虽然只包含一个大的分页功能,但是潜在的元素
包含:上一页 下一页 首页 尾页 当前页 等等。
为什么包含这些元素 ?==> 理解业务的能力
梳理元素之间的关系
上一页 == 当前页左移
下一页 == 当前页右移
首页 == 当前页左移到第一个
尾页 == 当前页右移动到最后一个
切换页码== 重新建立一个上一页、下一页、首页、尾页、当前页之间的关系逻辑
三、代码设计
1. 使用MVC模式梳理
为什么选择MVC模式
为什么不上来就干 (基本的从上至下,从左至右的标准规则)
可不可以选择其他设计模式
1.1 MVC简介
MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
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 使用抽象进行组件封装加入与外部交互
- 添加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模式的重点在于,需求变化的可控性 (至上)和程序员的一点骄傲(唯心)