代码要写成别人看不懂的样子(十)

本文介绍了如何使用状态模式管理对象行为变化,避免条件判断的复杂性,并通过策略模式解决商品促销策略的代码冗余,提升代码可维护性。

本篇文章参考书籍《JavaScript设计模式》–张容铭

前言

  各位写代码的时候,经常会出现条件判断吧,那么条件判断里面脸最熟的当属 if 了吧,这个东西在我们开始编码的时候,真的是隔一段时间不写,就浑身难受。

  但是呢,if 这个东西,哪怕是再简单的判断,也会有隐含问题,而且在代码可读性上,条件判断的效果很不友好,当存在多重判断的时候,简直就是灾难。

  所以为了解决条件判断的这些弊端,状态模式应运而生。

状态模式

  状态模式:当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象。

  我们可以将不同的状态结果封装在状态对象内部,然后该状态对象返回一个可被调用的接口方法,用于调用状态对下内部某种方法。做法如下:

//状态对象
var ResultState = function() {
	//结果保存在内部状态中
	var States = {
		//每种状态作为一种独立方法保存
		state0:funciton() {
			//处理结果0
			console.log('这是第一种情况');
		},
		state1:funciton() {
			//处理结果1
			console.log('这是第二种情况');
		},
		state2:funciton() {
			//处理结果2
			console.log('这是第三种情况');
		},
		state3:funciton() {
			//处理结果3
			console.log('这是第四种情况');
		},
	}
	//获取某一种状态,并执行其方法
	function show(result) {
		States['state' + result] && States['state' + result]();
	}
	return {
		//返回调用状态方法接口
		show: show
	}
} ();

  当我们想调用第三种结果的时候,我们就可以按照下面这种方法实现。

//展示结果3
ResultState.show(3);

  上面方法展示了状态模式的基本雏形,该模式主要目的就是将条件判断的不同结果转化为状态对象的内部状态。由于是内部状态,所以创建时设置成私有变量,只提供一个接口给外部进行增删改查,方便了我们对状态对象中,内部对象的管理。

  大家小时候都玩过超级玛丽吧,我们操作人物的时候可以跳跃,奔跑,蹲下,丢飞镖等,这些都是一个一个的状态,如果我们用 if 或者 switch 条件判断的话,那么后期会相当难维护,因为增加或者删除一个状态需要修改的地方太多了,这时使用状态模式帮我们管理就再合适不过了。

  对于超级玛丽,有的时候需要跳起来丢飞镖,有的时候需要蹲下丢飞镖,有的时候需要跑起来丢,这些组合状态如果用 if 或者 switch 判断的话,无形当中增加的成本是无法想象的。例如:

//单动作条件判断 每增加一个动作就需要添加一个判断
var lastAction = '';
function changeMarry(action) {
	if(action = 'jump') {
		//跳跃动作
	} else if(action = 'move') {
		//移动动作
	} else {
		//默认情况
	}
	lastAction = action;
}
//符合动作对条件判断的开销是翻倍的
var lastAction1 = '';
var lastAction2 = '';
function changeMarry(action1, action2) {
	if(action1 = 'throw') {
		//丢飞镖
	} else if(action1 = 'jump') {
		//跳跃
	} else if(action1 = 'jump' && action2 = 'throw') {
		//跳起来扔飞镖
	} else if(action1 = 'move' && action2 = 'throw') {
		//移动中扔飞镖
	}
	//保留上一个动作
	lastAction1 = action1 || '';
	lastAction2 = action2 || '';
}

  上面代码的可维护性和可读性都是很差的,日后对于新动作的添加或者修改原有动作的成本都是很大的。那么为了解决这一问题我们引入状态模式。解决思路如下:

  1.创建一个状态对象。

  2.内部保存状态变量。

  3.内部封装好每种动作对应的状态。

  4.状态对象返回一个接口对象,可以对内部状态进行修改或者调用。

//创建超级玛丽状态类
var MarryState = function() {
	//内部状态私有变量
	var _currentState = {},
		//动作与状态方法映射
		states = {
			jump: function() {
				//跳跃
				console.log('jump');
			},
			move: function() {
				//移动
				console.log('move');
			},
			throw: function() {
				//丢飞镖
				console.log('throw');
			},
			squat: function() {
				//蹲下
				console.log('squat');
			}
		};
	//动作控制类
	var Action = {
		//改变状态方法
		changeState: function() {
			//组合动作通过传递多个参数实现
			var arg = arguments;
			//重置内部状态
			_currentState = {};
			//如果有动作则添加动作
			if(arg.length) {
				//遍历动作
				for(var i = 0; len = arg.length; i < len; i++) {
					//向内部状态中添加动作
					_currentState[arg[i]] = true;
				}
			}
			//返回动作控制类
			return this;
		},
		//执行动作
		goes: function() {
			console.log('触发一次动作');
			//遍历内部状态保存的动作
			for(var i in currentState) {
				//如果该动作存在则执行
				states[i] && states[i]();
			}
			return this;
		}
	}
	//返回接口方法change ,goes
	return {
		change: Action.changeState,
		goes: Action.goes
	}
}

  超级玛丽的状态创建完成了,接下来就是使用了。

//创建一个超级玛丽
var marry = new MarryState();
marry
	.change('jump', 'throw')  //添加跳跃与丢飞镖动作
	.goes()          //执行动作
	.goes()          //执行动作
	.change('throw') //添加丢飞镖动作
	.goes();         //执行动作

  输出结果如下:

//触发一次动作
//jump
//throw
//触发一次动作
//jump
//throw
//触发一次动作
//throw

  改变状态类一个状态,就改变了状态对象的执行结果,是不是有点类似对象,让这些状态管理一下清晰了起来。

策略模式

  各位最近刚过完双十一吧,参与了将近五千亿的项目,感觉怎么样?凑满减的时候是不是感觉自己的数学水平又重新到达巅峰了。双十一活动有很多哈,比如满300减40,满200减25等等,有的商品不参与满减活动,但是可以先付定金,然后再付尾款,这一点像不像上面的刚介绍的状态模式,会用到很多条件判断。

  但是,情况又不太一样,不管商品采用哪种方案,一般都会只选取其中一种,比如该商品满300减40了,就不参与满200减25的活动了,如果使用状态模式的话,那就会为每一种商品,创建一个状态对象,这样做就会产生代码冗余。

  这个时候就需要用策略模式来解决当前问题了。策略模式结构上与状态模式很像,都是再内部封装一个对象,然后返回的接口对象实现对内部对象的调用。

  不同点是,策略模式不需要管理状态,状态间没有依赖关系,策略之间可以相互替换,在策略对象内部保存的是相互独立的一些算法。

//价格策略对象
var PriceStrategy = function() {
	//内部算法对象
	var strategy= {
		//满300减40
		reduce40: function(price) {
			return +price - ~~(price / 300) * 40; //~~表示parseInt
		},
		//满200减25
		reduce25: function(price) {
			return +price - ~~(price / 200) * 25;
		},
		//定金50付尾款
		deposit: function(price) {
			return +price - 50;
		},
	}
	//策略算法调用接口
	return function(algorithm, price) {
		//如果算法存在则调用算法,否则返回false
		return strategy[algorithm] && strategy[algorithm](price)
	}
} ();

  策略对象已经写出来了,我们可以看看怎么获取。

var price = PriceStrategy('reduce40', 1000);
console.log(price);    // 880

  策略模式其实应用的场景很多,比如 JQuery 动画中的缓冲函数,验证表单时候的正则算法。

//表单正则策略对象
var InputStrategy = function() {
	var strategy = {
		//是否为空
		notNull: funetion(value) {
			return /\s+/.test(value) ? '请输入内容' : '';
		},
		//是否是一个数字
		number: funetion(value) {
			return /^[0-9]+(\.[0-9]+)?$/.test(value) ? '' : '请输入数字';
		},
	}
	return {
		//验证接口 type 算法 value 表单值
		check: function(type, value) {
			//去除收尾空白符
			value = value.replace(/^\s+|\s+$/g, '');
			return strategy[type] ? strategy[type](value) : '没有该类型检测方法';
		},
		addStrategy: function(type, fn) {
			strategy[type] = fn;
		}
	}
} ();

  上面例子中,我们新增加了一个 addStrategy 用来增加新的策略,这个接口可以帮我们不用修改策略对象内部方法,便可以新增新的策略。用法如下:

//拓展可延续算法
InputStrategy.addStrategy('nickname', function(value) {
	return /^[a-zA-Z]\w{3,7}$/.test(value) ? '' : '请输入4-8位昵称, 如:LHXX';
});

  本片文章的两个设计模式还是很常见的,也比较简单,大家用一两次就能学会,使用后会给以后需求增加带来很多方便。




<think>好的,我现在需要帮助用户让一个C++新手理解之前的代码。首先,我得回顾一下之前的对话,用户的问题是关于读取和输出双精度浮点数,保留12位小数。用户现在希望解释这段代码,让新手能明白。 首先,我要确定用户的需求是什么。他们可能是一个刚学C++的学生,或者刚开始接触编程的人,对语法和库函数不太熟悉。所以需要将代码分解,逐步解释每个部分的作用,使用简单易懂的语言,避免专业术语过多,或者至少解释清楚术语。 接下来,我需要回顾之前的代码代码包括头文件iostream和iomanip,使用命名空间std,主函数中读取一个double变量,然后使用cout配合fixed和setprecision输出。对于新手来说,每个部分都可能有问题,比如头文件的作用、using namespace std是什么、double是什么,输入输出流的使用,以及格式控制的含义。 然后,我应该分步骤解释代码,从整体结构开始,比如每个头文件的作用,为什么需要包含iomanip。然后解释主函数的结构,变量的声明,输入语句cin,再到输出的格式控制。重点在于解释fixed和setprecision的作用,因为这是保留12位小数的关键。同时,要指出为什么需要这些控制符,比如默认情况下cout可能会用科学计数法,而fixed确保小数点后的位数固定。 还要注意可能新手会犯的错误,比如忘记包含iomanip导致setprecision不可用,或者没有使用fixed导致输出格式不符合要求。需要强调这些细节的重要性。另外,代码中的endl的作用是什么,和"\n"的区别,是否需要解释?可能对新手来说,endl除了换行还有刷新缓冲区的功能,但这里可能暂时不需要深入,以免增加复杂度。 此外,代码示例中的注释是否足够清晰?可能需要更详细的注释,或者在解释时逐步拆解。例如,解释using namespace std的作用,避免新手不理解为什么可以直接用cin和cout而不用std::前缀。 可能还需要举例子,比如输入一个数,输出会是什么样子,特别是当小数位数不足12位时,是否会补零。例如,输入3.14,输出会是3.140000000000,这样新手可以直观地看到结果。 还要考虑新手可能对函数和库的理解不足,比如main函数的结构,return 0的意义,变量声明的位置等等。是否需要简要解释这些基础内容?可能用户已经知道这些,但为了确保全面性,还是应该提到。 最后,总结整个代码的执行流程,从输入到处理到输出,每一步的作用,帮助新手形成整体的认识。确保语言通俗易懂,避免过于技术性的描述,用比喻或实际例子辅助理解。</think>让我们用更简单的方式分解这段代码,帮助新手逐步理解: ### 代码详解(适合新手版) ```cpp // ① 引入工具包 #include <iostream> // 输入输出必备工具(cin/cout) #include <iomanip> // 格式控制工具(fixed/setprecision) // ② 使用标准工具库(避免 std:: 前缀) using namespace std; // ③ 所有C++程序都必须有main函数,像程序的入口大门 int main() { // ④ 声明一个双精度浮点变量(能存带小数点的数) double num; // ⑤ 从键盘读取输入 >> 像水流进变量 cin >> num; // ⑥ 格式化输出(重点!) cout << fixed // 固定小数显示(不用科学计数法) << setprecision(12) // 显示12位小数(不够补零) << num // 输出数值 << endl; // 换行(相当于按下回车键) // ⑦ 告诉系统程序正常结束 return 0; } ``` ### 关键概念解释表 | 代码部分 | 作用说明 | 类比理解 | |-------------------|--------------------------------------------------------------------------|------------------------------| | `#include <...>` | 导入功能模块,就像玩游戏前要下载安装包 | 做菜前要准备好食材和厨具 | | `using namespace` | 使用标准工具库的缩,避免每次都`std::` | 说"手机"不用每次都加品牌前缀 | | `double num;` | 创建一个可以存储小数的容器(双精度浮点型),比`float`精度更高 | 准备一个高精度电子秤 | | `cin >> num` | 从键盘输入数值,存入`num`容器 | 把食材放进电子秤称重 | | `fixed` | 固定小数点显示模式(如不用这个,大数会显示成`1.23e+5`这样的科学计数法) | 让电子秤始终显示千克单位 | | `setprecision(12)`| 精确控制显示12位小数(第13位会四舍五入) | 设置电子秤显示到0.000000克 | | `cout << ...` | 把结果输出到屏幕 | 把称重结果打印到标签上 | ### 举个实际例子 🌰 **输入:** ```bash 3.141592653589793 ``` **输出:** ```bash 3.141592653590 ``` (注意:第13位是`8`,四舍五入后第12位`9`变成`10`,进位后显示为`90`) ### 新手常见疑问解答 ❓ 1. **为什么要用`double`不用`float`?** - `double`精度更高(15-17位有效数字),`float`只有6-9位,存12位小数必须用`double` 2. `fixed`和`setprecision`的顺序重要吗? - 非常重要!必须先`fixed`,否则`setprecision`控制的是总有效位数(比如`cout << setprecision(3) << 12.345`会输出`12.3`) 3. **输入123会怎样显示?** - 输出`123.000000000000`,自动补足12位小数 4. **能处理超大数吗?** - 可以!`fixed`模式会完整显示,比如输入`123456789.123456789`会显示为`123456789.123456789000` 建议新手自己尝试修改代码: 1. 去掉`fixed`观察输出变化 2. 把12改成其他数字(如3或15) 3. 输入超过12位小数的数观察四舍五入效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值