美文网首页设计模式系列教程
设计模式系列教程—Compound Pattern(复合模式)

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

作者: Vander1991 | 来源:发表于2019-05-14 23:45 被阅读0次

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

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

    面向对象基础

    抽象、封装、多态、继承

    九大设计原则

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

    模式

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

    相关文章

      网友评论

        本文标题:设计模式系列教程—Compound Pattern(复合模式)

        本文链接:https://www.haomeiwen.com/subject/gmgyaqtx.html