Javascript | 模拟mvc实现点餐程序

690 阅读4分钟

关注公众号,一起交流,微信搜一搜: LiOnTalKING

前言

MVC模式是一个比较成熟的开发模式。M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。

今天就来模拟使用MVC模式开发一个点餐程序,当然,只是就此案例来说明MVC在前端的实现参考,并没有完整的实现。程序很简单,与传统的MVC三层架构不谋而合。

首先,先介绍一下场景:

顾客进到餐馆,拿着菜单点餐,服务员记录并发到后端厨房,厨师接到订单,按照订单的菜品去制作,制作完毕通知服务员取餐,服务员将菜品送到客户餐桌,顾客就餐,完毕。我们看一下业务流程图:

在这个案例中,M对应的是菜品订单,C对应的就是服务员,V对应的就是顾客。服务员将顾客和厨师连接到一起,但是顾客并不直接与厨师沟通与接触。服务员需要监听菜品的完成情况,一旦完成,厨师会通知服务员取餐,并送到顾客餐桌。我们看一下最终的界面:

界面共分为4个区域:

菜单: 顾客进到餐馆的第一个动作,拿菜单点菜。

已点:顾客点完菜,即生成点菜订单。

制作:后厨根据订单制作菜品。

餐桌:制作完成的菜品会被端到顾客的餐桌。

程序用H5实现,包含一个展现的页面order.html,一个order.js。order.js包含两个类:Food(食物类)和Controller(点餐控制器)。

菜单的实现如下:

<h1>请点餐</h1>
<div id="menu" style="border: 1px dashed darkkhaki;width: 25%;text-align: center;"><span>糖醋排骨</span><span>菜花</span><span>焖猪脚</span><span>红烧鲤鱼</span><span>鱼头汤</span></div><br />
		

当然,得响应菜单的选择,要实现这个功能,我们还得引入食物和控制器。

首先,看一下食物类的定义:

/**
 * 食物
 * @param {Object} name 名称
 */
function Food(name) {
	this.name = name;
	this.finished = false; //是否烹饪完成
	this.listeners = []; //监听器,作用:厨房把菜做好,需要通知服务员上菜
};

注册监听器:

/**
 * 监听食物的完成
 * @param {Object} callback 注册回调函数,食物制作完成调用
 */
Food.prototype.watch = function(callback) {
	this.listeners.push(callback);
};

这里的监听器在实际的场景中当然是指上菜员了,从程序架构角度这里是支持多个监听器的,很多业务场景下一个数据的改变可能会影响到多个界面的更新。这个点餐程序传入的实际就是一个回调函数,食物制作完成则调用该函数更新界面显示(上菜)。

最后,食物制作完成:

/**
 * 食物制作完成
 */
Food.prototype.finish = function() {
	var self = this;
	self.finished = true;
	console.log('食物制作完成: ' + self.name);
	// 食物烹饪完成时,应通知前台(监听器)上菜	
	// 按照Javascript事件处理的一般机制,我们异步地调用回调函数	
	// 如果觉得setTimeout影响性能,也可以采用requestAnimationFrame
	setTimeout(function() {
		self.listeners.forEach(function(listener) {
			listener(self);
		});
	});
};

食物制作完成,轮询监听器,执行回调函数。

接下来,引入点餐控制器,这可是数据和界面的纽带(后厨和顾客)。

看控制器定义:

/**
 * 点餐控制器
 */
function Controller() {
	//食物列表
	this.foods = [];
};

这个控制器里面存放了顾客点的菜品。

当然了,控制器还应包括点餐:

//点餐
this.order = function(name) {
	var food = new Food(name);   //创建食物
	food.watch(this.handle);     //监听食物的完成
	this.foods.push(food);       //将该食物加到食物列表
};

这里的this.handle就是传入的回调函数。定义如下:

//上菜
this.handle = function(food) {
	console.log('这道菜已经烹饪完毕:' + food.name);
	refreshKitchen();    //刷新后厨制作的显示
	refreshTable();      //刷新餐桌的显示
};

另外,这个控制器应该还包含查找指定的食物:

//查找已点食物
this.get = function(name) {
	for (var i = 0; i < this.foods.length; i++) {
		if (this.foods[i].name == name) {
			return this.foods[i];
		}
	}
	return null;
};

好了,至此,order.js的全部内容已经完成。

回到页面,我们需要在页面的javascript里面定义控制器的实例:

//创建控制器
var controller = new Controller();

定义程序启动的函数:

//程序启动
(function startApp() {
	//客户点菜,加入到已点列表
	var menus = document.getElementById('menu').getElementsByTagName('span');
	for (var i = 0; i < menus.length; i++) {
	    menus[i].onclick = function(e) {
	    var foodName = e.target.innerText;
	    //点餐
	    controller.order(foodName);
	    //刷新 已点、后厨制作、餐桌的显示
	    refresh();
	};
    }
})();

这里就包含了响应最开始的点餐事件。包含两个动作:点餐和刷新界面显示。实际的业务场景中,顾客点了菜之后会形成待制作的订单录入到系统并发送到后厨,厨师制作菜品。顾客的手机订单中也能查看到已点的菜品以及可能的制作的动态信息。

刷新界面的方法包含:

/**
 * 刷新
 */
function refresh() {
	refreshOrdered();
	refreshKitchen();
};
/**
 * 刷新已点
 */
function refreshOrdered() {
	var ordered = document.getElementById('ordered');
	var html = '';
	for (var i = 0; i < controller.foods.length; i++) {
		html += '<li>' + controller.foods[i].name + '</li>';
	}
	ordered.innerHTML = html;
};
/**
 * 刷新后厨
 */
function refreshKitchen() {
	var kitchen = document.getElementById('kitchen');
	var html = '';
	var btn = '<input type="button" value="完成" style="margin-left: 10px;" onclick="finishFood(this);"/>';
	for (var i = 0; i < controller.foods.length; i++) {
		if (controller.foods[i].finished == false) {
			html += '<li>' + controller.foods[i].name + btn + '</li>';
		}
	}
	kitchen.innerHTML = html;
};
/**
 * 刷新餐桌
 */
function refreshTable() {
	var table = document.getElementById('table');
	var html = '';
	for (var i = 0; i < controller.foods.length; i++) {
		if (controller.foods[i].finished == true) {
			html += '<li>' + controller.foods[i].name + '</li>';
		}
	}
	table.innerHTML = html;
};

界面的几块显示区域定义:

<fieldset style="width: 20%;">
			<legend>已点</legend>
			<ul id="ordered">
			</ul>
		</fieldset>
		<fieldset style="width: 20%;">
			<legend>后厨制作</legend>
			<ul id="kitchen">
			</ul>
		</fieldset>
		<fieldset style="width: 20%;">
			<legend>餐桌</legend>
			<ul id="table">
			</ul>
		</fieldset>

区分几块区域的样式:

<style type="text/css">
			#menu span {
				background-color: antiquewhite;
				margin-right: 10px;
			}

			#kitchen li {
				color: chocolate;
				margin-right: 10px;
			}

			#table li {
				color: blue;
				margin-right: 10px;
			}
		</style>

后厨制作里面,每个食物后面会有一个完成按钮:

单击完成

/**
 * 食物烹饪完成
 */
function finishFood(btn) {
	var li = btn.parentNode;
	var food = controller.get(li.innerText);
	food.finish();
};

最后,食物的监听器就会被调用,并刷新后厨、餐桌的显示。

至此,这个点餐程序全部完成。