Java 面向对象 · 交互课程

示例4 · Erase MVC 版

把单机版 Erase 按 MVC(Model-View-Controller)重构:GameBoard/Shape 只含状态与规则、GameView 只读重绘、GameController 是唯一状态修改入口,组合根 EraseApp 装配三层——可 headless 单测、可替换视图。

示例4 · Erase MVC 版

定位:「MVC 模式」章的配套运行示例。把 示例3 · Erase 网络版 的单体 GameBox(状态+规则+渲染混一类)按 MVC 拆成 model / view / controller 三个包,演示职责分离如何带来可测试性与可替换性。

源码位置:仓库根目录 素材/Erase-MVC/(含 src/mvc/...、test/ModelTest.java、docs/1-MVC结构设计方案.md)。

目录

一、运行方式

在 素材/Erase-MVC/ 下(JDK 8+,纯 Swing 无需额外依赖):

cd 素材/Erase-MVC
javac -encoding UTF-8 -d out src/mvc/model/*.java src/mvc/view/*.java src/mvc/controller/*.java src/mvc/EraseApp.java
java -cp out mvc.EraseApp

运行后出现双人对战窗口:P1 用 w s a d,P2 用 ↑ ↓ ← →;菜单可开始/暂停/继续。

二、包结构与职责

包类职责
mvc.modelGameBoard游戏状态 + 规则(移动/旋转校验、消行计分、结束判定);状态变更 fireStateChanged()
Shape形状数据 + 移动/旋转/生成(无 Graphics 绘制)
GameStateListener状态变更通知接口 stateChanged(GameBoard)
mvc.viewGameView单玩家面板渲染:只读 Model,收到 stateChanged 后 repaint()
GameFrame主窗口:菜单、布局、按键监听挂载(不修改游戏状态)
mvc.controllerGameController每玩家一个:按键分发 + 200ms Timer 驱动 + 菜单命令,唯一状态修改入口
mvcEraseApp组合根:创建并装配 M/V/C(三层唯一交汇点)

依赖方向(单向、可审计):Controller→Model、View→Model(只读)、Model→View(仅经 GameStateListener 接口回调)。Model 不 import 任何 AWT/Swing 类。

三、对照单机版:改了什么

关注点单机版(erase)MVC 版(mvc)
状态 + 规则GameBox(混渲染)model.GameBoard(零 Swing)
渲染GameBox.paint*view.GameView(只读重绘)
计时/按键/菜单GameBox.TimerListener + 5 个监听器散落controller.GameController(统一入口)
数据类Shape(带 Graphics 绘制)model.Shape(纯数据/规则)
可测试性必须启动窗口人工观察test/ModelTest.java headless 自动验证规则

四、headless 单元测试

因为 Model 零 Swing 依赖,可以不开窗口直接验证规则:

cd 素材/Erase-MVC
javac -encoding UTF-8 -d out src/mvc/model/*.java test/ModelTest.java
java -cp out ModelTest

ModelTest 构造 GameBoard、调用 handleKey/addscore 等方法并断言状态——这正是 MVC「职责分离」带来的直接收益。

教学衔接:本示例是「MVC 模式」章的运行载体;交互组件 <MVCDataFlow />(见该章)把「一次按键」的跨层数据流动画成时序图。