MVC 程序设计思想

随着应用越来越大,代码越来越多,实际编写难度越来越复杂。因此需要有一种程序设计思想指导编程。

MVC全名为 Model View Controller,是模型 model视图 view控制器 controller 的缩写,是一种程序设计典范。MVC 通过一种业务逻辑、数据、界面显示分离的方法组织代码,将相同的业务逻辑聚集到一个部件里面,使得在对程序改进时,不需要重新编写底层逻辑。

生命游戏

以生命游戏为例,通过描述一段时间内生命游戏世界的变化进行说明 MVC 的思想。

生命游戏一个由二维矩阵构成的世界,这个世界中的每个方格居住着一个活着的的或死了的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;相反,如果周围活细胞过少,这个细胞会因太孤单而死去。

游戏规则

  1. 如果一个细胞周围有 3 个细胞为生,则该细胞为生。
  2. 如果一个细胞周围有 2 个细胞为生,则该细胞的状态保持不变;
  3. 在其它情况下,该细胞死亡。

MVC 设计思想

MVC 作为指导思想,编写生命游戏。

问题分析

生命游戏以 Cell 作为主导,关注 Cell 的状态及其变化。

通过 Cell 填充大小为 $(width, height)$ 而形成的矩阵 Field,构建了对Cell的管理类型。Field 作为 Cell 的上一级对所有 Cell 进行统一管理。

控制器 CellMachine 通过创建好的规则驱动 Field 中每一个 Cell 进行状态更新,记录每一步 Cell 的运行状态,实时显示 Cell 运行状态。

实例编写

这里以 python 阐述的计算机语言,通过面向对象的思想实现三个模块。

Model 由两部分构成,CellFieldField 作为所有 Cell 对外接口。

View 以文本形式输出,则无需该部分,通过 print 或是写入文件方式,显示实时状态。 ViewGUI 作为可视化输入,需要引入 GUI 模块进行可视化。

Controler 作为主程序入口,集中控制,显示运行状态,传送各部分消息。

Model

以面向对象的思想,确定 Cell 状态(),定制状态改变接口

Cell.py

class Cell(object):
    """细胞-显示单元"""

    def __init__(self, state=1):
        """初始化`Cell`状态

        Keyword Arguments:
            state {int} -- `Cell`情况,默认死亡 (default: {1})
        """

        self.state = state

    def __repr__(self):
        """以字符形式表示细胞情况

        Returns:
            str -- "□" - 活着,"■" - 死亡
        """

        return "□" if self.isAlive() else "■"

    def die(self):
        """设置`Cell`状态为死亡
        """

        self.state = 1

    def reborn(self):
        """设置`Cell`状态为活着
        """

        self.state = 0

    def isAlive(self):
        """判断细胞状态

        Returns:
            bool -- True - 活着,False - 死亡
        """

        return True if self.state == 0 else False

    def draw(self, cv, row, col, size):
        """图像显示

        Arguments:
            cv {tkinter.cv} -- tkinter 绘图接口
            row {int} -- 行
            col {int} -- 列
            size {int} -- 大小
        """

        if self.state == 1:
            color = 'black'
        else:
            color = 'white'
        cv.create_rectangle(
            col * size,
            row * size,
            (col + 1) * size,
            (row + 1) * size,
            fill=color
        )

通过 Field 集中管理所有 Cell 状态,设定控制所有 Cell 更新的接口,预留状态输入函数

Field.py

class Field(object):
    """`Cell`集合-模型"""

    def __init__(self, width, height):
        """主界面

        Arguments:
            width {int} -- 宽度
            height {int} -- 高度
        """

        self.width = width
        self.height = height
        self.field = np.array(
            [[Cell() for i in range(self.width)]
             for i in range(self.height)]
        )

    def __repr__(self):
        """响应

        Returns:
            str -- 返回所有`Cell`状态
        """

        return '\n'.join(["|".join([str(c) for c in i]) for i in self.field])

    def __setitem__(self, index, cell):
        """对某坐标`Cell`状态设置

        Arguments:
            index {[int, int]} -- `Cell`坐标
            cell {Cell} -- 状态覆盖
        """

        self.field[index] = cell

    def getNeighbour(self, row, col):
        """获取某坐标的附近的邻居

        Arguments:
            row {int} -- 该`Cell`所在行
            col {int} -- 该`Cell`所在列
        """

        for r in np.arange(-1, 2) + row:
            for c in np.arange(-1, 2) + col:
                if (-1 < r < self.height and
                    -1 < c < self.width and
                        not (r == row and c == col)):
                    yield self.field[r][c]

    def __getitem__(self, index):
        """返回某坐标`Cell`的状态

        Arguments:
            index {[int, int]} -- `Cell`坐标

        Returns:
            Cell -- `Cell`状态
        """

        return self.field[index]
View

集中控制,GUi 显示效果

View.py

class View(object):
    """显示器"""

    def __init__(self, name, field, gridSize=16):
        """
        Arguments:
            name {str} -- 标题名
            field {Field} -- `Cell`管理器

        Keyword Arguments:
            gridSize {int} -- 网络大小 (default: {16})
        """

        self.field = field
        self.gridSize = gridSize
        self.win = tkinter.Tk()
        self.win.title(name)
        self.cv = tkinter.Canvas(
            self.win,
            width=self.field.width * gridSize,
            height=self.field.height * gridSize
        )
        self.cv.pack()

    def grid(self, gridSize):
        """绘制网格

        Arguments:
            gridSize {int} -- 网格大小
        """

        for i in range(1, self.field.height + 1):
            self.cv.create_line(
                0,
                i * gridSize,
                gridSize * self.field.width,
                i * gridSize
            )

        for i in range(1, self.field.width + 1):
            self.cv.create_line(
                i * gridSize,
                0,
                i * gridSize,
                self.field.height * gridSize
            )

    def paint(self):
        """网格内容绘制
        """

        for row in range(self.field.height):
            for col in range(self.field.width):
                self.field[row][col].draw(
                    self.cv,
                    row,
                    col,
                    self.gridSize
                )
        self.win.update()

    def close(self):
        """窗口运行
        """

        self.win.mainloop()
Controler

控制器,实现 Cell 的更新规则

CellMachine.py

class CellMachine(object):
    """生命游戏主程序-控制器"""

    def __init__(self, name="Cell Machine", width=15, height=15, gridSize=16):
        """
        Keyword Arguments:
            name {str} -- 标题 (default: {"Cell Machine"})
            width {int} -- 宽度 (default: {15})
            height {int} -- 高度 (default: {15})
            gridSize {int} -- 网格大小 (default: {16})
        """

        self.field = Field(width, height)
        self.view = View(name, self.field, gridSize)

    def setFistGeneration(self, p=.5):
        """初始化`Cell`状态,将所有`Cell`默认置为死亡状态,以一定概率活过来

        Keyword Arguments:
            p {float} -- `Cell`活过来的概率 (default: {.5})
        """

        for row in range(self.field.width):
            for col in range(self.field.height):
                if random.random() < p:
                    self.field[row][col].reborn()

    def step(self):
        """更新所有`Cell`状态
        """

        for row in range(self.field.width):
            for col in range(self.field.height):
                lived = [i for i in self.field.getNeighbour(row, col)
                        if i.isAlive()]
                numOfLive = len(lived)

                if self.field[row][col].isAlive():
                    if numOfLive < 2 or numOfLive > 3:
                        self.field[row][col].die()
                elif numOfLive == 3:
                    self.field[row][col].reborn()

    def start(self, times, sleep=False):
        """生命游戏启动器

        Keyword Arguments:
            times {int} -- 运行次数
            sleep {float} -- 运行间隔 (default: {False})
        """

        self.view.grid(self.view.gridSize)
        for cnt in range(times):
            try:  # stop view.paint function break error
                self.step()
                self.print(cnt)
                self.view.paint()
                if sleep:
                    time.sleep(.15)
            except:
                break

    def print(self, i):
        """命令行显示

        Arguments:
            i {int} -- 代数
        """

        print('-' * self.field.width * 2)
        print()
        print('Generation>> {}'.format(i))

        print('-' * self.field.width * 2)
        print('-' * self.field.width * 2)
        print(self.field)


if __name__ == '__main__':
    cellMachine = CellMachine(width=15, height=15, name='_(:з」∠)_')
    cellMachine.setFistGeneration()
    print(cellMachine.field)
    cellMachine.start(times=100)

sourcecode

面向过程的编程

以面向过程的程序设计作为指导,前期无需考虑太多只需要约定 Cell状态标识,并以此形成矩阵,根据规则更新 Cell 状态。

但若涉及到后续的程序扩展(图像化界面,规则更改,状态更新,状态记录)等方面内容,处理起来尤为困难。

他话

MVC 程序设计思想需要在前期规划整体应用设计方向,预估 Model 可能的规模,因此在前期需要投入额外的精力。