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

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

实现效果:

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:


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

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

面向对象基础

抽象、封装、多态、继承

九大设计原则

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

模式

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

你可能感兴趣的:(设计模式系列教程—Compound Pattern(复合模式))