设计模式系列教程

设计模式系列教程—Compound Pattern(复合模式)

2019-05-14  本文已影响0人  Vander1991

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);
    }
}

最后又到了喜闻乐见的总结部分,我们又来总结我们现在现有的设计模式武器。

面向对象基础

抽象、封装、多态、继承

九大设计原则

设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程
设计原则三:多用组合,少用继承
设计原则四:为交互对象之间的松耦合设计而努力
设计原则五:对扩展开放,对修改关闭
设计原则六:依赖抽象,不要依赖于具体的类
设计原则七:只和你的密友谈话
设计原则八:别找我,我有需要会找你
设计原则九:类应该只有一个改变的理由

模式

复合模式:复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题。

上一篇 下一篇

猜你喜欢

热点阅读