14 Compound Pattern(复合模式)
前言:由模式组成的模式。
需求:
Vander的业务继续发展壮大,现在他们公司已经开始了设计游戏,由于前期想先设计一些比较简单的游戏来增加自己团队的经验,首先先让自己团队练练手,以下是CEO兼CTO的Vander给出的游戏设计方案:
经典的猜数字游戏:
简单分析一下,要完成这个游戏,首先需要跟用户交互的视图(View),接收请求的控制器(Controller),实现具体功能的模型(Model)。
下面直接引出MVC模式:
1、策略模式:视图和控制器实现了策略模式,控制器是视图的行为,想要不同的行为,换个控制器即可。视图只关心界面显示,对于任何界面的行为都委托给控制器处理。另一方面,控制器负责和模型交互来传递用户的请求,使得视图和模型间的关系解耦。
2、组合模式:显示包括了窗口、面板。按钮和文本标签等,每个显示组件都是组合节点或者叶子节点,当控制器告诉视图更新时,只需告诉视图的顶层组件,组合会处理具体的事情。
3、观察者模式:当模型的状态改变时,相关的对象会持续更新,相当于模型的状态改变会去通知视图、控制器作出相应的变化,使用观察者模式,可以让模型完全独立于视图和控制器。
首先我们要想清楚逻辑之间的交互是怎么样的,Model这个类中肯定有观察者的列表,此时的观察者就是视图,所以它需要提供更新视图状态的操作(这个操作一般发生在Model本身处理完一些状态之后,此处是UpdateGame()方法),还需要提供注册视图观察者的操作。其余的方法就是Model的一些处理逻辑,如接收到输入的number后,进行范围判断,返回提示等逻辑操作。
接下来是控制器部分,控制器是View和Model的粘合剂,它组合了View和Model,用户按下视图的Play按钮之后,将请求委托给控制器,控制器再将请求委托给Model进行相应的操作。值得一提的是,控制器在这里还需要有创建视图的操作。
最后是视图部分,视图主要是创建视图界面的操作,还有视图作为观察者,需要实现UpdateGame方法,并且视图实现了策略模式,组合了控制器,用户操作界面的一系列操作会调用控制器的相应方法。视图还需要将自己添加到Model的观察者列表中。
BaseGameController:
public interface BaseGameController {
void start();
void play(int number);
}
GameController:
public class GameController implements BaseGameController {
BaseGameModel model;
GameView view;
public GameController(BaseGameModel model) {
this.model = model;
view = new GameView(model, this);
view.createControlView();
view.enableStartMenuItem();
}
public void start() {
view.disableStartMenuItem();
model.restart();
}
public void play(int number) {
model.play(number);
}
}
BaseGameModel:
public interface BaseGameModel {
void play(int number);
void restart();
void registerObserver(GameObserver o);
}
GameModel:
public class GameModel implements BaseGameModel {
ArrayList gameObservers = new ArrayList();
private int max;
private int min;
private int result;
private String tips;
/**
* 生成随机数
* @return
*/
public int getRandomNum() {
Random rand = new Random();
int randomNum = rand.nextInt(100);
return randomNum;
}
/**
* 实现猜测游戏的逻辑
* @param number
* @param result
* @return
*/
public void play(int number) {
if(number >= min && number <= max ) {
if(number > result) {
max = number;
} else if(number < result){
min = number;
} else {
tips = "您猜对啦!";
updateGame();
return;
}
}
tips = "请输入" + min + "-" + max + "的整数";
updateGame();
}
/**
* 重新开始游戏需要重置一下范围
*/
public void restart() {
min = 0;
max = 100;
tips = "请输入" + min + "-" + max + "的整数";
result = getRandomNum();
updateGame();
}
/**
* 注册视图观察者
*/
public void registerObserver(GameObserver o) {
gameObservers.add(o);
}
/**
* 当model完成相关逻辑后,更新观察者(视图)的状态
*/
public void updateGame() {
for(GameObserver gameObserver : gameObservers) {
gameObserver.updateRange(tips);
}
}
}
GameObserver:
public interface GameObserver {
void updateRange(String tips);
}
GameView:
public class GameView implements GameObserver, ActionListener {
JFrame controlFrame;
JMenu menu;
JMenuItem startMenuItem;
BaseGameModel model;
BaseGameController controller;
JMenuBar menuBar;
JLabel gameTipsLabel;
JTextField gameNumberTextField;
JButton playButton;
JButton restartButton;
public GameView(BaseGameModel model, BaseGameController controller) {
super();
this.model = model;
this.controller = controller;
model.registerObserver((GameObserver)this);
}
public void createControlView() {
// 创建Frame,设置大小和位置
controlFrame = new JFrame("猜数字游戏");
controlFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
controlFrame.setSize(new Dimension(400, 200));
controlFrame.setLocation(500, 500);
//添加菜单项,Start、Stop、Quit
menu = new JMenu("菜单");
startMenuItem = new JMenuItem("开始游戏");
menu.add(startMenuItem);
startMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
controller.start();
}
});
JMenuItem quit = new JMenuItem("退出游戏");
quit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
});
menu.add(quit);
//将创建好的菜单放入菜单栏,然后将菜单栏加入到Frame中
menuBar = new JMenuBar();
menuBar.add(menu);
controlFrame.setJMenuBar(menuBar);
gameTipsLabel = new JLabel("请输入1-100的整数", SwingConstants.LEFT);
gameNumberTextField = new JTextField(2);
playButton = new JButton("Play");
playButton.setSize(new Dimension(10,40));
playButton.addActionListener(this);
restartButton = new JButton("Restart");
restartButton.setSize(new Dimension(10,40));
restartButton.addActionListener(this);
JPanel labelPanel = new JPanel(new GridLayout(1, 1));
labelPanel.add(gameTipsLabel);
JPanel playPanel = new JPanel(new GridLayout(1, 3));
playPanel.add(gameNumberTextField);
playPanel.add(playButton);
playPanel.add(restartButton);
JPanel controlPanel = new JPanel(new GridLayout(2, 1));
controlPanel.add(labelPanel);
controlPanel.add(playPanel);
gameTipsLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
controlFrame.getRootPane().setDefaultButton(playButton);
controlFrame.getContentPane().add(controlPanel, BorderLayout.CENTER);
controlFrame.setVisible(true);
}
public void disableStartMenuItem() {
startMenuItem.setEnabled(false);
}
public void enableStartMenuItem() {
startMenuItem.setEnabled(true);
}
public void actionPerformed(ActionEvent event) {
if(event.getSource() == playButton) {
int number = Integer.parseInt(gameNumberTextField.getText());
controller.play(number);
} else if(event.getSource() == restartButton) {
controller.start();
}
}
public void updateRange(String tips) {
gameTipsLabel.setText(tips);
}
}
Main:
public class Main {
public static void main(String args[]) {
BaseGameModel model = new GameModel();
BaseGameController gameController = new GameController(model);
}
}
实现效果:
随着游戏的受欢迎程度越来越大,Vander决定进军页游行业。所以要将游戏移植到网页端。
MVC模式在如今的Web开发已经广为流传,其中早起的Model2模型应用广泛,它使用Servlet和JSP技术相结合来达到MVC的分离效果。
简单分析一下上图
1、你发出一个会被Servlet收到的Http请求
你利用浏览器,发出Http请求,通常涉及到送出的表单数据,例如用户名和密码。Servlet收到这样的数据,并解析数据。
2、Servlet扮演控制器
Servlet扮演控制器的角色,处理你的请求,通常会向模型(一般是数据库)发出请求,处理结果往往以JavaBean的形式打包。
3、控制器将控制权交给视图
视图是JSP,而JSP唯一的工作就是产生页面,表现模型的视图(模型通过JavaBean中取得)以及进一步大动作所需要的所有空间
4、模型通过JavaBean中取得
5、视图通过Http将页面返回浏览器
页面返回浏览器,作为视图显示出来,用户进一步请求,以同样的方式处理。
Model2不仅提供了设计上的组件分割,也提供了“制作责任”的分割,以前后端程序员甚至需要自己编写HTML代码,需要有精湛的前端技术才能完成Web的开发,而现在该编程的编程,该做页面的做页面,大家专业分工,责任清楚。
此处需要创建dynamic web project,具体的创建过程。
请参考https://blog.csdn.net/Vander1991/article/details/80808985
这里值得一提的是建立动态Web工程,需要在Pom中制定打包方式(war)。
GameServlet:
public class GameServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
GuessService guessService;
public GameServlet() {
guessService = new GuessService();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int number = Integer.parseInt(request.getParameter("number"));
HttpSession session = request.getSession();
String tips = guessService.getTips(number, session);
request.setAttribute("tips", tips);
request.getRequestDispatcher("/game.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
RestartServlet:
public class RestartServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
GuessService guessService;
public RestartServlet() {
guessService = new GuessService();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int result = guessService.getRandomNum();
HttpSession session = request.getSession();
session.setAttribute("result", result);
String tips = guessService.start(session);
request.setAttribute("tips", tips);
request.getRequestDispatcher("/game.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
GuessService:
public class GuessService {
/**
* 生成随机数
* @return
*/
public int getRandomNum() {
Random rand = new Random();
int randomNum = rand.nextInt(100);
return randomNum;
}
/**
* 实现猜测游戏的逻辑
* @param number
* @param result
* @return
*/
public String getTips(int number, HttpSession session) {
String tips = "";
int result = (Integer) session.getAttribute("result");
int min = (Integer) session.getAttribute("min");
int max = (Integer) session.getAttribute("max");
if(number >= min && number <= max ) {
if(number > result) {
max = number;
} else if(number < result){
min = number;
} else {
tips = "您猜对啦!";
return tips;
}
}
session.setAttribute("min", min);
session.setAttribute("max", max);
tips = "请输入" + min + "-" + max + "的整数";
return tips;
}
/**
* 重新开始游戏需要重置一下范围
*/
public String start(HttpSession session) {
String tips = "";
int min = 0;
int max = 100;
session.setAttribute("min", min);
session.setAttribute("max", max);
tips = "请输入" + min + "-" + max + "的整数";
return tips;
}
}
pom:
4.0.0
szu.vander
head-first-design-pattern
0.0.1-SNAPSHOT
14-mvc-model2
14-mvc-model2
14-mvc-model2
war
javax.servlet
javax.servlet-api
3.1.0
javax.servlet
servlet-api
2.5
provided
javax.servlet.jsp
jsp-api
2.2
org.apache.maven.plugins
maven-compiler-plugin
3.6.1
1.8
game.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
猜数字游戏
请输入0-100的整数!
web.xml:
Guess Number Game
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
GameServlet
GameServlet
szu.vander.game.web.GameServlet
GameServlet
/play
RestartServlet
RestartServlet
szu.vander.game.web.RestartServlet
RestartServlet
/start
实现效果:
这种方式似乎比较繁琐,下面用目前比较流行的Spring Boot再实现一遍,前端页面使用的是Thymeleaf模板。
GuessService:
@Service
public class GuessService {
/**
* 生成随机数
* @return
*/
public int getRandomNum() {
Random rand = new Random();
int randomNum = rand.nextInt(100);
return randomNum;
}
/**
* 实现猜测游戏的逻辑
* @param number
* @param result
* @return
*/
public String getTips(int number, HttpSession session) {
String tips = "";
int result = (int) session.getAttribute("result");
int min = (int) session.getAttribute("min");
int max = (int) session.getAttribute("max");
if(number >= min && number <= max ) {
if(number > result) {
max = number;
} else if(number < result){
min = number;
} else {
tips = "您猜对啦!";
return tips;
}
}
session.setAttribute("min", min);
session.setAttribute("max", max);
tips = "请输入" + min + "-" + max + "的整数";
return tips;
}
/**
* 重新开始游戏需要重置一下范围
*/
public String start(HttpSession session) {
String tips = "";
int min = 0;
int max = 100;
session.setAttribute("min", min);
session.setAttribute("max", max);
tips = "请输入" + min + "-" + max + "的整数";
return tips;
}
}
GameController:
@Controller
public class GameController {
@Autowired
private GuessService guessService;
@RequestMapping(value="/play", method= {RequestMethod.POST, RequestMethod.GET})
public ModelAndView play(int number, HttpSession session) {
ModelAndView mv = new ModelAndView();
String tips = guessService.getTips(number, session);
mv.addObject("tips", tips);
mv.setViewName("game");
return mv;
}
@RequestMapping(value="/start", method= {RequestMethod.POST, RequestMethod.GET})
public ModelAndView start(HttpSession session) {
int result = guessService.getRandomNum();
session.setAttribute("result", result);
String tips = guessService.start(session);
ModelAndView mv = new ModelAndView();
mv.addObject("tips", tips);
mv.setViewName("game");
return mv;
}
}
GameThymeleafApplication:
@SpringBootApplication
public class GameThymeleafApplication {
public static void main(String[] args) {
SpringApplication.run(GameThymeleafApplication.class, args);
}
}
最后又到了喜闻乐见的总结部分,我们又来总结我们现在现有的设计模式武器。
面向对象基础
抽象、封装、多态、继承
九大设计原则
设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程
设计原则三:多用组合,少用继承
设计原则四:为交互对象之间的松耦合设计而努力
设计原则五:对扩展开放,对修改关闭
设计原则六:依赖抽象,不要依赖于具体的类
设计原则七:只和你的密友谈话
设计原则八:别找我,我有需要会找你
设计原则九:类应该只有一个改变的理由
模式
复合模式:复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题。