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

本文介绍了JavaScript中的三种设计模式:建造者模式用于复杂对象的构建,通过步骤化创建;原型模式利用对象克隆实现类的快速实例化,减少性能开销;单例模式确保类只有一个实例,常用于管理全局资源。文中还探讨了单例模式的延时加载(惰性单例)应用。

前言

  上节我们一块学习了工厂相关的设计模式,可以方便的帮我们创建类或者实例。本节我们换个角度,不关心产出什么实例,我们来研究一下实例生产的过程。

  这里有个问题,为什么要关注生产过程,我们要的不都是最后的实例对象吗?话是没错,但是有些实例太复杂了,我们需要一步一步划分好功能,依次创建。才能完成最终对象的创建。

  举个例子,我们现在想要一套房子,房子是一个对象没错,我们还要关心这个房子有没有卧室,厨房怎么样,客厅大不大等等。那么这些关注点,都是需要我们创建的。

一、建造者模式

  关注建造细节,下面我们创建一个简历的构造函数。

//创建人类
var Human = function(param) {
	//技能
	this.skill = param && param.skill || '保密';
	//兴趣爱好
	this.hobby = param && param.hobby || '保密';
}
//类人原型方法
Human.prototype = {
	getSkill: function() {
		renturn this.skill;
	},
	getHobby: function() {
		renturn this.hobby;
	}
}
//实例化姓名类
var Named = fucntion(name) {
	var that = this;
	//构造器
	//构造函数解析姓名的姓与名
	(function(name, that) {
		if(name.indexOf(' ') > -1) {
			that.firstName = name.slice(0, name.indexOf(' '));
			that.secondName = name.slice(name.indexOf(' '));
		}
	}) (name, that);
}
//实例化职位类
var Work = function(work) {
	var that = this;
	//构造器
	//构造函数中通过传入的职位特征来设置相应职位以及描述
	(function(work, that) => {
		switch(work) {
			case 'code':
				that.work = '工程师';
				that.workDescript = '沉迷编程无法自拔';
				break;
			case 'UI':
			case 'UE':
				that.work = '设计师';
				that.workDescript = '设计是一种艺术';
				break;
			case 'teach':
				that.work = '教师';
				that.workDescript = '我分享,我快乐';
				break;
			default :
				that.work = work;
				that.workDescript = '对不起,我们还不清楚职位的相关描述';
		}
	}) (work, that);
}
//更换期望的职位
Work.prototype.changeWork = function(work) {
	this.work = work;
}
//更改对职位的描述
Work.prototype.changeDescript = function(setence) {
	this.workDescript = setence;
}

  我们的最终目的是创建一位应聘者,创建过程需要用到上面抽象的三个类,这时候需要一个建造者类,在建造者类中,我们需要通过对三个类的组合调用,创建出一个完整的应聘者对象。

/******
 * 应聘者建造者
 * 参数 name : 姓名(全名)
 * 参数 work : 期望职位
 */
var Person = function(name, work) {
    //创建应聘者缓存对象
    var _person = new Human();
    //创建应聘者姓名解析对象
    _person.name = new Named(name);
    //创建应聘者期望职位
    _person.work = new Work(work);
    return _person;
}

  上面,我们通过三部分来创建一位应聘者。

var person = new Person('xiao ming', 'code');

  建造者模式与工厂最大的不同,就是建造者会参与具体的创建,干涉建造细节,生成的对象,也更复杂。

二、原型模式

  接下来要介绍的设计模式,是整个语言的灵魂,原型模式。

  原型模式指的是:用原型实例指向创建对象的类,使用于创建新的对象的类共享原型对象的属性以及方法。

  举个例子,我们现在创建一个轮播图对象,不同的轮播图效果不一样,有左右滑动的,还有上下滑动的,另外渐变的轮播图也有。

//图片轮播类
var LoopImages = function(imgArr, container) {
	this.imagesArray = imgArr; //轮播图片数组
	this.container= container; //轮播图片容器
	this.createImage = function() {} //创建轮播图片
	this.changeImage = function() {} //切换下一张图片
}
//上下滑动切换类
var SlideLoopImg = function(imgArr, container) {
	//构造函数继承图片轮播类
	LoopImages.call(this, imgArr, container);
	//重写继承的切换下一张图片方法
	this.changeImage = function() {
		console.log('SlideLoopImg changeImage function');
	}
}
//渐隐切换类
var FadeLoopImg = function(imgArr, container, arrow) {
	LoopImages.call(this, imgArr, container);
	//切换箭头私有变量
	this.arrow = arrow;
	this.changeImage = function() {
		console.log('FadeLoopImg changeImage function');
	}
}

  创建一个显隐轮播图片来测试

var fadeImg = new FadeLoopImg([
	'01.jpg',
	'02.jpg',
	'03.jpg',
	'04.jpg'
], 'slide', [
	'left.jpg',
	'right.jpg'
])
fadeImg.changeImage();

  上面代码已经实现了我们需要的功能,但是还有一部分可以优化,比如 LoopImage 作为基类,始终是要被子类继承的,那么把一些方法写到父类的构造函数里就会存在一部分性能问题,因为每次子类继承都要创建一次父类,如果父类里面有很多耗时操作,那么每次创建都会有很大的性能开销,这时候如果把这些耗时操作放到父类的原型上,那么就会节约一大部分性能。

//图片轮播类
var LoopImages = function(imgArr, container) {
	this.imagesArray = imgArr; //轮播图片数组
	this.container= container; //轮播图片容器
	//下面两部分我们移到原型当中,这样就不会每次实例化父类的时候都执行这些耗时操作了
	//this.createImage = function() {} //创建轮播图片
	//this.changeImage = function() {} //切换下一张图片
}
LoopImages.prototype = {
	//创建轮播图片
	createImage: function() {
		console.log('LoopImages createImage function');
	} ,
	//切换下一张图片
	changeImage : function() {
		console.log('LoopImages changeImage function');
	} 
}
//上下滑动切换类
var SlideLoopImg = function(imgArr, container) {
	//构造函数继承图片轮播类
	LoopImages.call(this, imgArr, container);
}
SlideLoopImg.prototype = new LoopImages();
//重写继承的切换下一张图片
SlideLoopImg.prototype.changeImage = function() {
	console.log('SlideLoopImg changeImage function');
}

//渐隐切换类
var FadeLoopImg = function(imgArr, container, arrow) {
	LoopImages.call(this, imgArr, container);
	//切换箭头私有变量
	this.arrow = arrow;
}
FadeLoopImg.prototype = new LoopImages();
FadeLoopImg.prototype.changeImage = function() {
	console.log('FadeLoopImg changeImage function');
}

  原型对象还有一个特点,由于原型对象是一个共享的对象,那么不管父类的实例还是子类的实例,都是对它的指向引用,如果原型对象进行扩展,那么无论是子类还是父类,都会继承下来。所以在扩展的时候,一定要慎重。

LoopImages.prototype.getImageLength = function() {
	return this.imagesArray.length;
}
FadeLoopImg.prototype.getContainer = function() {
	return this.getContainer ;
}
console.log(fadeImg.getImageLength);
console.log(fadeImg.getContainer);

三、单例模式

  接下来一起学习最常用的一种模式,一个人的狂欢。单例模式只允许实例化一次对象类。这种模式的出现,是为了更好的管理我们书写的方法,比如 A 写了一个 getElement 的方法, B 也想写,但是命名又不能一样,这就需要我们对自己的方法进行整理归类,比如我们定义一个自己的命名空间 MySelf

var Myself = {
	getElement: function(id) {
		return document.getElementById(id)
	},
	changeType: function(id, key, value) {
		this.getElement(id).style[key] = value;
	}
}

  上面的代码,就是我们自己的小型代码库,任何我们用到的方法都可以放到这个里面去,当我们想要使用的时候,通过 Myself.getElement 就可以。有了 Myself ,这个命名空间,我们就不用担心会和别人的命名冲突。 JQuery 就是前辈们的一个命名空间,里面有着很多方便我们使用的方法。当我们整理的比较好的时候,也可以发布自己的代码库供别人使用。

单例模式妙用

  单例模式还有一个妙用,就是用来管理静态变量。由于在es6之前,JS 中并没有 static 这个关键字,所以是没有静态变量的,但是我们可以手动实现静态变量的功能。

  所谓静态变量,就是指那些,可以访问,但是不能修改的变量。

var Conf = (
	//私有变量
	var conf = {
		MAX_NUM: 100,
		MIN_NUM: 1,
		COUNT: 1000
	}
	//返回取值器方法
	return {
		//取值器方法
		get: function(name) {
			return conf[name] ? conf[name] : null;
		}
	}
)();

var count = Count.get('COUNT');
console.log(count); //1000

惰性单例

  有时候单例对象需要延迟创建,所以还有一种延迟创建的单例模式。

//惰性载入单例
var = LazySingle = (function() {
	//单例实例引用
	var _instance = null;
	//单例
	function Single() {
		/*这里定义私有属性和方法*/
		return {
			publicMethod: function() {},
			publicProperty: '10'
		}
	}
	//获取单例对象接口
	return function() {
		//如果为创建单例,则创建单例
		if(!_instance) {
			_instance = Single();
		}
		//返回单例
		return _instance;
	}
})();

console.log(LazySingle().publicProperty) //1.0

  到这里,我们已经介绍完了所有关于“创”,“建”,类型的设计模式,从最开始的工厂相关模式,到后面的建造者,原型,单例,这部分包含的功能可以涵盖我们代码的大部分区域,各位同学自己在编码的时候,可以多试试每种模式,我个人的建议是,先按住一个,然后往死里用,合适不合适都这样用,等到什么时候快用吐了,你就真的掌握这种思想了。

  




<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位小数的数观察舍五入效果
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值