Niffler

7.7 模块划分与软件设计技术

Posted By sysu-swsad-team

前端技术应用

在前端我们使用了我们将用到下面几个框架与工具:

Vue.js 组件化应用构建

我们选择 Vue.js 作为我们前端开发的主要框架。Vue.js 是一套构建用户界面的渐进式框架,它只关注视图层,采用自底向上增量开发的设计,目标是通过尽可能简单的 API 实现响应的数据绑定和组合的试图组件,并且 Vue 学习起来非常简单。

组件系统是 Vue 的一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。跟大多数应用界面一样,在我们的前端应用界面系统可以抽象成下面的一个组件树,Vue 的组件化特性非常适合我们的应用构建。

components

我们利用 Vue 的组件化特性,将前端界面划分为组件,比如登录/注册界面是一个组件,它又包含登录/注册的子组件。每个组件分文件管理,组件化构建应用可以让我们方便地复用组件。

比如在 Login.vue 里,引用 LoginForm.vue 组件和 RegisterForm.vue 组件

<template>
  <div id="root">
    <div class="App">
      <div class="Panel FormPanel">
        <template v-if="signIn">
          <h2>NIFFLER</h2>
          <div class="Social">
          </div>
          <p id="niffler-desc">一个可以赚闲钱的 APP</p>
          <LoginForm class="sign-in-out-form"></LoginForm>
        </template>
        <template v-else>
          <h2 id="signup-title">SIGN UP</h2>
          <div class="Social">
          </div>
          <RegisterForm class="sign-in-out-form"></RegisterForm>
        </template>
      </div>
      ...
    </div>
  </div>
</template>

<script>
import LoginForm from './LoginForm'
import RegisterForm from './RegisterForm'
export default {
  data () {
    return {
      signIn: true,
      transition: false
    }
  },
  components: {
    LoginForm,
    RegisterForm
  },
  methods: {
    ...
  }
}
</script>

Vuex 状态管理

在构建 Vue 单页面应用时,Vuex 为我们提供了全局状态管理。在 state 中定义的数据后,我们可以在项目中任一个组件中进行获取或修改,并且你的修改可以得到全局的相应变更。当我们想要在项目中定义和使用全局变量时,使用 Vuex 是一个很好的解决方法。

  • state:对数据的全局存储
  • getter:和 Vue计算属性 computed 一样,来实时监听 state 值的变化 (最新状态)
  • mutations:对数据的同步更改
  • actions:对数据的异步更改

Vuex:

vuex

在 src/vuex/store.js 中:

import Vue from 'vue'
import Vuex from 'vuex'

import logo from '@/assets/images/niffler-logo.png'

Vue.use(Vuex)

// 要设置的全局访问的state对象
const state = {
  // 要设置的初始属性值
  isCollapse: false,
  sysname: 'NIFFLER',
  ...
}

// 实时监听state值的变化(最新状态)
const getters = {
  // 承载变化的isCollapse的值
  getIsCollapse (state) {
    return state.isCollapse
  },
  ...
}

// mutations: 对数据的同步更改
const mutations = {
  mutationsChangeSideBar (state) {
    state.isCollapse = !state.isCollapse
  },
  ...
}

// actions: 对数据的异步更改
const actions = {
  changeSideBar (context) {
    context.commit('mutationsChangeSideBar')
  },
  ...
}

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions
})

export default store

Element-UI 组件库

Element-UI 是一套基于 Vue 2.x 的组件库, 它提供了构建常见网站的的各种组件,如页面布局、按钮、表单、数据表格、导航菜单、对话框等。使用 Element-UI 能够快速构建一个单页面应用网站。

如在 LoginForm.vue 中使用 Element-UI 实现登录表单:

<el-form v-if="isRememberPW" :model="ruleForm" status-icon :rules="rules" ref="ruleForm" class="demo-ruleForm">
  <el-form-item prop="email">
    <el-input type="text" v-model="ruleForm.email" placeholder="Email"></el-input>
  </el-form-item>
  <el-form-item prop="password">
    <el-input type="password" show-password v-model="ruleForm.password" placeholder="Password"></el-input>
  </el-form-item>
  <div class="link"><a href="/#/login" @click="isRememberPW = true">忘记密码?</a></div>
  <el-form-item style="width:100%">
    <el-button
    :loading="logining"
    @click.native.prevent="handleLogin"
    type="primary" class="form-btn">Sign in</el-button>
  </el-form-item>
</el-form>

webpack

通常,我们在构建网页应用时,会有繁杂的 JavaScript 代码和一大堆依赖包。简单来说,webpack 可以看做是模块打包机,它帮助分析项目结构,把项目的所有依赖文件打包成一个或多个浏览器可以识别的 JavaScript 文件。选择 webpack 来构建我们的应用能够方便管理、开发与部署。

前端文件结构

src
│   App.vue			# 程序入口
│   main.js			# main
│   utils.js		# 一些函数
│
├───api				  # axois api接口
│       api.js 			
│       index.js
│
├───assets			# 资源文件
│
├───mock 			  # mock桩测试代码
│
├───page			  # 组件
│   │   404.vue		    # 404页面
│   │   Root.vue 	    # 根页面,包含header
│   │
│   ├───Balance		    # 充值与提现界面
│   │       Balance.vue
│   │
│   ├───components	  # 应用组件
│   │       EditQuestionair.vue		        # 编辑问卷组件
│   │       ModuleCards.vue			          # 模块卡组件
│   │       PageHead.vue			            # 页头组件
│   │       ShowQuestionnaire.vue	        # 显示问卷组件
│   │
│   ├───Errand		    # 跑腿办事页面
│   │       Errand.vue				            # 跑腿办事主页主组件
│   │       Create.vue				            # 创建任务组件子组件
│   │       Mine.vue				              # 我的任务组件子组件
│   │
│   ├───Home		      # 主页面
│   │       Home.vue			                # 主页面主组件
│   │       Message.vue				            # 我的信息子组件
│   │       PersonInfo.vue		            # 个人信息子组件
│   │
│   ├───Login		      # 登录/注册页面
│   │       Login.vue				              # 登录/注册页面主组件
│   │       LoginForm.vue		              # 登录表单子组件
│   │       RegisterForm.vue		          # 注册表单子组件
│   │
│   └───Questionnaire	  # 问卷系统页面
│           Questionnaire.vue		          # 问卷系统主组件
│           Create.vue				            # 创建问卷子组件
│           Mine.vue				              # 我的问卷子组件
│
├───router		# 路由
│       index.js
│
├───styles		# 组件用到的样式(可复用)
│       pages.scss
│       vars.scss
│
└───vuex		  # vuex状态管理
        store.js

后端设计

模块划分

后端同样使用 MVC 架构,其中 Model 可以用来与数据库交互, 具体如下:

  • User(Django 自建用户)
  • Profile (扩展的用户资料)
  • EmailVerify (负责存储邮箱验证码和有效期)
  • Task(”问卷” 或 “跑腿” 任务)
  • Participantship(用户参与信息)
  • Tag(”用户” 或 “任务” 的标签)

建立好实体了,接着就需要多功能进行模块划分,划分的好处是让系统结构更加清晰,模块和模块之前相互解耦。同时对于多人协作的项目来说,可以独立分配一个模块进行开发。

其中设计的模块有:

  • 权限管理模块(包括用户的注册登录登出)
  • 内容模块(包括查看个人资料和当前进行中的所有任务)
  • 功能模块(包括 “问卷” “跑腿” “充值/提现” 三大功能)
  • 发布模块(包括修改个人资料,创建问卷,发起委托,参与任务等)
  • 评论模块(包括发布者取消和确认任务,参与者提出争议和评论问卷等)

django-mvt

后端此次没有使用模版文件代表的 View,所以没有图形显示界面,对外显示的功能体现为对前端请求的响应。当前端请求到来时,路由模块调用(Controller)提供的接口,即 url.py 中的各种访问入口,进入 views.py 中不同的处理函数,并且在其中通过 model 与数据库交互,之后响应请求返回给用户。


软件设计技术

命令模式

在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合,这就是命令模式(Command Pattern)。

在 Django 中,把 get和 post 这些请求的行为抽象成一个 HttpRequest 类,我们可以通过 request 实例的method 获取行为的类型:

if request.method == 'GET':
    do_something()
elif request.method == 'POST':
    do_something_else()

此外,对于不同的数据,Django 还分别抽象了相应的类来处理。

# Json数据:
from django.http import JsonResponse
>>> response = JsonResponse({'foo': 'bar'})
>>> response.content
b'{"foo": "bar"}'

# File数据:
from django.http import FileResponse
>>> response = FileResponse(open('myfile.png', 'rb'))

观察者模式

观察者模式(有时又被称为发布(publish)-订阅(Subscribe)模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

Django 中,存在 Singal 与 dispatch 类,分别用来发送和接受信号,实现被观察与观察者的角色。

定义一个观察者,接收一种特定的信号:

import django.dispatch
 
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"]

发送信号,使观察者接收相应的信息:

class PizzaStore(object):
    ...
 
    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self.__class__, toppings=toppings, size=size)

RESTful 设计

在 Django Restframework 框架中的 APIView 类中,提供了put(), delete(), patch() 方法的支持。

如上传一个文件,对应的 view:

class FileUploadView(views.APIView):
    parser_classes = (FileUploadParser,)
 
    def put(self, request, filename, format=None):
        file_obj = request.data['file']
        # ...
        # do some stuff with uploaded file
        # ...
        return Response(status=204)

可以针对每一个 model 定义一个 View 类,并根据不同的请求方法开发不同的处理函数。

django-mvt

如本次的 TaskView 继承自 ViewSetViewSet 在最后的实例化时,会将 list, create 等方法绑定到 get, post, put, patch, delete 等 REST 操作对应的处理函数(通常由 Router 创建 URL 定义时自动绑定),最后绑定到 model-list, model-create, model-detail 等 URL。

同样也可以手动 ViewSet 绑定到 URL:

path('task/<pk>/', views.TaskView.as_view({'get': 'retrieve'})),
path('task/', views.TaskView.as_view({
  'get': 'get',
  'post': 'create'
  })),
path('task/cancel/<pk>/', views.TaskView.as_view({'post': 'cancel'})),
path('task/claim/<pk>/', views.TaskView.as_view({'post': 'claim'})),

面向对象设计

面向对象程序设计是一种程序设计范型,也是一种程序开发的方法。把对象作为程序的基本单元,将程序和数据封装其中,以此来提高软件的重用性、灵活性、和扩展性。面向对象设计可以看做是一种在程序中包含各种独立而又相互调用的对象的思想,这与传统的思想不同,传统的思想更侧重于把程序看作一系列函数的集合,而面向对象设计中的每一个对象都能够接收数据、处理数据和把数据传达给其他对象。

在后端开发中,我们使用了ORM,ORM全称是Object Relational Mapping(对象关系映射),它在编程中把面向对象的概念跟数据库中表的概念对应起来,定义一个对象,就对应着一张表,这个对象的实例,就对应着表中的一条记录。ORM封装了底层的数据库操作,降低了编程难度,提高了代码的可读性和易维护性。

在模型niffler-backend/niffler/questionnaire/models.py中,我们定义了EmailVerify、Profile、Task等等几个类,这些类继承了django.db.models.Model,这些类定义了数据库的行为,每个类在数据库中都有一张表,我们在类中定义了一些属性,这些属性对应了表的不同字段,同时我们也在类中定义一些方法,让这些类不仅有数据库的存储和查询功能,还能进行一些计算,比如Task.status这种计算属性,使得这个计算行为只存在模型中,而对视图和控制器是透明的,很好地揭藕,有利于扩展、移植和维护。

在视图niffler-backend/niffler/questionnaire/views.py中,我们根据niffler-backend/niffler/questionnaire/models.py的类定义相应的视图集(也是一个类,继承了rest_framework.viewsets.ViewSet),比如ProfileView。对于一个视图集,我们可以重载rest_framework.viewsets.ViewSet的一些属性,如用于验证的authentication_classes,我们也定义了一些方法,比如retrieve()get()post(),对应不同的服务操作,而模型的不同类都会有retrieve、get、post这种相似的操作,相比将所有操作写成并列的视图函数这种做法,面向对象使得编程结构更加简洁和清晰,代码更具可读性,增删方法也更加灵活,也能够复用类似authentication_classes的相同属性,增加内聚性。