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

本文介绍了JavaScript中的两种设计模式——外观模式和适配器模式。外观模式通过提供一个简单的接口,封装了复杂的底层操作,如事件绑定的兼容处理。适配器模式则用于解决接口不兼容问题,例如在前后端分离中,让前端能灵活应对后台数据结构的变化。文章通过实例展示了这两种模式的实际运用,并讨论了它们在实际项目中的价值。

前言

  通过前面几篇文章,大家估计对对象的创建有了一个新的认识,在很多大型应用里面,对象会相当复杂,但是其刚创建的时候,并不是这样,而是通过后续的组合,封装,才一步一步完善起来的,从本章节开始,我们一块来学一学,如何把这些对象组合的更合理,从而使想强大的功能。

一、外观模式

  大家一定绑定过事件,而且一定像下面这样绑定过

document.onclick = fucntion(e) {
	e.preventDefault();
	if(e.target !== document.getElementById('myInput')) {
		hideAlert();
	}
}
function hideAlert() {
	//隐藏提示框
}

  上面方法没问题,功能也可以实现,但是如果团队中有人也给 document 绑定了 onclick 事件的话,那么后面的就会覆盖前面的。

  所以上面功能应该使用 addEventListener 来添加监听事件,这样即使添加多个,也只是叠加,不会覆盖。同时还需要注意,老版本 IE 不支持 addEventListener 要改为 attachEvent ,当然,如果有浏览器版本也不支持 addEventListener 的话,还需要另行处理。

  作为一名程序员,我们一定要时刻思考,怎么能用最少的成本,做最多的事。

  对付这种情况,我们就可以用外观模式来行进封装。

  举个例子,去学校食堂吃饭,用餐的人很多,可选择的菜品有很多,还有一群人有选择困难症,这样吃一顿饭就会有很多时间消耗在选择吃什么上,这个时候食堂为了提高效率,推出了套餐,比如宫保鸡丁套餐,可以提供米饭,菜,饮料等。我们不用遍历每一种菜,注释吃什么,饮料喝什么,因为套餐已经定制好了。

  在 JS 中,我们可以通过点个“套餐”,来简化复杂的需求。这样就提供了一个高级接口,简化了对复杂底层接口不统一的情况。

//外观模式实现
function addEvent(dom, type, fn) {
	//支持 addEventListener的浏览器
	if(dom.addEventListener) {
		dom.addEventListener(type, fn, false)
	//支持 attachEvent的浏览器
	} else if(dom.attachEvent) {
		dom.attachEvent('on' + type, fn);
	//支持 on + '事件名'的浏览器
	} else {
		dom['on' + type] = fn;
	}
}

  这样一来我们就可以安心的为元素添加点击事件了

var input = document.getElementById('myinput');
addEvent(input, 'click', function() {
	console.log('绑定的第一个事件');
})
addEvent(input, 'click', function() {
	console.log('绑定的第二个事件');
})

  由上面的实例我们可以看出,外观模式在解决浏览器兼容问题上,应用比较多,那我们可以继续改造上面的代码,对事件参数 event 来进行兼容处理。

//获取事件对象
var getEvent = function(event) {
	return event || window.event;
}
//获取元素
var getTarget = function(event) {
	return event.target || event.srcElement;
}
//组织默认行为
var preventDefault = function(event) {
	var event = getEvent(event);
	if(event.preventDefault) {
		event.preventDefault();
	} else {
		event.returnValue = false;
	}
}

  其实外观模式的不光在浏览器兼容方面大放异彩,它的核心思想是简化底层操作,比如代码库中,经常用外观模式来封装多个功能。

var A = {
	//通过id获取元素
	g: function(id) {
		return document.getElementById(id);
	},
	//设置元素css属性
	css: fucntion(id, key, value) {
		document.getElementById(id).style[key] = value;
	},
	//设置元素属性
	attr: function(id, key, value) {
		document.getElementById(id)[key] = value;
	},
	html: function(id, html) {
		document.getElementById(id)innerHTML = value;
	},
	//为元素绑定事件
	on: function(id, type, fn) {
		document.getElementById(id)['on' + type] = fn;
	}
};

//通过这个代码库,我们可以简化设置元素的属性设置
A.css('box', 'background', 'red');
A.attr('box', 'className', box);
A.html('box', '新增加的内容');
A.on('box', 'click', function() {
	A.css('box', 'width', '500px')
});

二、适配器模式

  接下来这个模式,专门解决头疼问题,说到适配器,就不得不提一家公司,苹果,这家公司总喜欢制定新规则,每次新产品发布,都会重组一大部分适配厂商。比如前几年的新款 mac 取消所有 usb 接口,一律改为 type_C ,这就导致所有人都得买个转换器,而官网的转换器 145 一个,还只有一个接口,心疼~

  好在绿联应运而生,价格实惠,输出稳定,还能一转多。

  不过最近的 iPhone12 充电线,大家自行体会~。

  如果有绿联公关部的同学看到这篇文章,请联系我结算一下广告费!

  有点跑题了哈,说到我们的适配器模式,这个模式就是上面那个 145 。顾名思义,适配器就是把接口不同的东西,转化成接口相适配的模式。

//这里有一个我们自己写的代码库
var A = {
	//通过id获取元素
	g: function(id) {
		return document.getElementById(id);
	},
	//为元素绑定事件
	on: function(id, type, fn) {
		document.getElementById(id)['on' + type] = fn;
	}
};

  我们如果我们把上面的代码库,适配一下 JQuery ,需要改成下面这样

var A = {
	//通过id获取元素
	g: function(id) {
		return $(id).get(0);
	},
	//为元素绑定事件
	on: function(id, type, fn) {
		var dom = typeof id === 'string' ? $('#' + id) : $(id);
		dom.on(type, fn);
	}
};

  上面的过程,就是一个适配器模式,除此之外适配器模式还有很多用途,比如当某个方法需要传多个参数时。

function doSomeThing(name, title, age, color, size, prize) {}

  上面你方法要记住这么多参数,还不能打乱顺序,比较困难,这时候用到适配器模式如下:

/**
*obj.name: name
*obj.title: title
*obj.age: age
*obj.color: color
*obj.size: size
*obj.prize: prize
***/
function doSomeThing(obj) {}

  上面这种格式在使用 TypeScript 做数据类型校验时极为方便。

  当然如果对参数格式要求不严格,某些参数没有时自动添加一个默认值,那么就可以按照下面的方式

function doSomeThing(obj) {
	var _adapter = {
		name: 'Murphy',
		title: '设计模式',
		age: '24',
		color: 'pink',
		size: 100,
		prize: 50
	}
	for(var i in _adapter) {
		_adapter[i] = obj[i] || _adapter[i];
	}
	//doSomthing
}

  不过适配器模式真正的用武之地是在前后端分离上,它让前端脱离了后台参数的限制,如果后端因架构改变导致数据结构发生变化,那我们只要写个适配器就可以了。

function ajaxAdapter(data) {
	//处理数据并返回新数据
	return [data['key1'], data['key2'], data['key3']]
}
$.ajax({
	url: 'someAdress.php',
	success: function(data, status) {
		if(data) {
			//使用适配后的数据--返回的对象
			doSomething(ajaxAdapter(data));
		}
	}
});

   JavaScirpt 中适配器的应用,更多的用在对象之间,为了使对象可用,通常我们会将对象进行拆分并重新包装,这就要求我们了解适配对象的内部结构,这正是与外观模式的最大不同。

  下一章节,我们来一起弄一个经常出现的问题,面试常考,平常也经常遇到的,跨域问题。




<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、付费专栏及课程。

余额充值