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<GameObserver> gameObservers = new ArrayList<GameObserver>();
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);
}
}
实现效果:
image.png随着游戏的受欢迎程度越来越大,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:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>szu.vander</groupId>
<artifactId>head-first-design-pattern</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>14-mvc-model2</artifactId>
<name>14-mvc-model2</name>
<description>14-mvc-model2</description>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
game.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>猜数字游戏</title>
<!-- 引入BootStrap的CSS -->
<link href="${pageContext.request.contextPath}/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="${pageContext.request.contextPath}/static/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/static/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript">
$(function() {
//使用JavaScript获取属性tips
var tips = '${tips}'
console.info("tips =" + tips);
$("h1").html(tips);
});
function play(){
var number = $("#number").val();
//正则表达式 :只允许填写数字
var regex = /^\d+$/;
if(regex.test(number)){
$("#playForm").submit();
} else {
//将h1设置成红色
$("h1").css("color", "#FF0000");
}
}
function start(){
$("#startForm").submit();
}
</script>
</head>
<body>
<body style="text-align: center;">
<h1>请输入0-100的整数!</h1>
<form action="play" class="form-inline" method="post" id="playForm">
<div class="input-group">
<input type="text" class="input-middle" name="number" id="number" />
<button type="button" class="btn btn-info" onclick="play()">提交</button>
<button type="button" class="btn btn-info" onclick="start()">重新开始游戏</button>
</div>
</form>
<form action="start" class="form-inline" method="post" id="startForm">
</form>
</body>
</body>
</html>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Guess Number Game</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>GameServlet</display-name>
<servlet-name>GameServlet</servlet-name>
<servlet-class>szu.vander.game.web.GameServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GameServlet</servlet-name>
<url-pattern>/play</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>RestartServlet</display-name>
<servlet-name>RestartServlet</servlet-name>
<servlet-class>szu.vander.game.web.RestartServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RestartServlet</servlet-name>
<url-pattern>/start</url-pattern>
</servlet-mapping>
</web-app>
实现效果:
这种方式似乎比较繁琐,下面用目前比较流行的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);
}
}
最后又到了喜闻乐见的总结部分,我们又来总结我们现在现有的设计模式武器。
面向对象基础
抽象、封装、多态、继承
九大设计原则
设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程
设计原则三:多用组合,少用继承
设计原则四:为交互对象之间的松耦合设计而努力
设计原则五:对扩展开放,对修改关闭
设计原则六:依赖抽象,不要依赖于具体的类
设计原则七:只和你的密友谈话
设计原则八:别找我,我有需要会找你
设计原则九:类应该只有一个改变的理由
模式
复合模式:复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题。
网友评论