// // DP1. 简单的工厂模式
// // 1. 为解决多个相似的问题
// function CarFactory(brand, price) {
// var car = new Object();
// car.brand = brand;
// car.price = price;
// car.getPrice = function () {
// return this.price;
// }
// return car;
// }
// var p1 = CarFactory("牌子A", 10000);
// var p2 = CarFactory("牌子B", 20000);
// console.log(typeof p1, typeof p2); // 返回都是object,无法识别它们是哪个对象的实例
// // 2. 优点:能解决多个相似问腿,减少代码冗余。专业术语叫做耦合度降低
// // 3. 缺点:对象的类型未知
// // DP2. 复杂工厂模式
// // 1. 定义:
// // 将其成员对象的实例化推迟到子类中,子类可以重写父类接口方法以便创建的时候制定自己的对象类型
// // 即父类为抽象类,处理子类共同业务,具体业务子类实现
// function ComplexCarFactory(brand, price) {
// this.brand = brand;
// this.price = price;
// }
// ComplexCarFactory.prototype = {
// constructor: ComplexCarFactory,
// sellCar: function () {
// var speed = this.getSpeed(this.brand);
// console.log(this.brand + '的车子售价:' + this.price + '元人民币,限速' + speed + '公里每小时');
// },
// getSpeed: function (brand) {
// throw new Error("父类是抽象类不能直接调用,需要子类重写该方法");
// }
// };
// var CarChild = function (brand, price) {
// this.brand = brand;
// this.price = price;
// // 继承构造函数父类中的属性和方法
// ComplexCarFactory.call(this, brand, price);
// };
// // 子类继承父类原型方法
// CarChild.prototype = Object.create(ComplexCarFactory.prototype);
// // CarChild 子类重写父类的方法
// CarChild.prototype.getSpeed = function (brand) {
// var speed = 0;
// if (brand === '牌子C') {
// speend = 100;
// }
// if (brand === '牌子A') {
// speend = 2000;
// }
// return speed;
// }
// var p3 = new CarChild("牌子C", 3000);
// console.log(p3); // CarChild {brand: "牌子C", price: 3000} // 可以看到实例对象类型
// console.log(p3 instanceof CarChild); // true
// console.log(p3.sellCar()); // 牌子C的车子售价:3000元人民币,限速100公里每小时
// // 解析:ComplexCarFactory为父类,CarChild为子类,CarChild继承自ComplexCarFactory
// // ComplexCarFactory不在进行对象实例化,只对创建过程中的一般性问题进行处理,ComplexCarFactory就像是Java中的抽象类,必须被子类重写,否则调用ComplexCarFactory的sellCall方法时就会抛出异常
// // CarChild继承自ComplexCarFactory,同时重写了父类的方法,CarChild类实例后的对象之间是相互独立的,具体的业务逻辑会放在子类中进行编写
// // 2. 优点:(面试官想听到的)
// // 弱化对象间耦合,防止代码重复
// // 重复性代码可以放到父类去编写,子类继承与父类的成员属性和方法。子类只专注于实现自己的业务逻辑
// // DP3. [创建型] 单例模式
// // 1. 单例模式限制某个类只能被创建一次,并且需要提供一个全局访问点
// // 2. 关键:如果一个类的实例不存在,单例模式就会创建一个新的类实例
// // 如果实例存在,它只返回对该对象的引用
// // 对构造函数的任何重复调用都会获取相同的对象
// // 3. 实例
// let instance = null;
// function User() {
// if (instance) {
// return instance;
// }
// instance = this; // instance指向this
// this.name = 'Matthew';
// this.age = 23;
// return instance;
// }
// const user1 = new User();
// const user2 = new User();
// console.log(user1 === user2); // true;
// // 4. 实例(使用揭示模块模式)
// const instance = (function () {
// let instance;
// function init() {
// return { name: 'Matthew', age: 23 };
// }
// function getInstance() {
// if (!instance) {
// instance = init();
// }
// return instance;
// }
// return {
// getInstance,
// }
// })();
// const instanceA = instance.getInstance();
// const instanceB = instance.getInstance();
// console.log(instanceA === instanceB); // true
// // 5. 应用场景:
// // 应用中需要一个全局且唯一的的对象
// // 6. 优点:
// // 适用于单一对象,只生成一个实例,避免频繁创建和销毁实例,减少内存占用
// // 7. 缺点:
// // 不适用动态创建对象,或者需要创建多个类似对象的场景
// var Singleton = function (name, age) {
// this.name = name;
// this.age = age;
// this.instance = null;
// };
// Singleton.prototype.getInfo = function () {
// console.log(`I am ${this.name}, i am ${this.age} years old.`);
// }
// function getInstance(name, age) {
// if (!this.instance) {
// this.instance = new Singleton(name, age);
// }
// return this.instance;
// }
// var a = getInstance('小王', 34);
// var b = getInstance('小红', 26);
// console.log(a === b); // true 只会被创建一次
// a.getInfo(); // I am 小王, i am 34 years old.
// b.getInfo(); // I am 小王, i am 34 years old.
// // 应用场景
// // 创建遮罩层
// var createMask = (function () {
// var div;
// return function () {
// if (!div) {
// div = document.createElement('div');
// div.className = "mask";
// div.innerHTML = "遮罩层";
// div.style.display = 'none';
// div.style.width = 800;
// div.style.height = 2400;
// div.style.background = 'green';
// document.body.appendChild(div);
// }
// return div;
// }
// })();
// document.addEventListener('click', function () {
// // debugger
// var win = createMask();
// win.style.display = 'block';
// });
// // 上述代码简单封装一下:
// var getInstance = function (func) {
// var result;
// return function () {
// return result || (result = func.call(this, arguments));
// }
// };
// // 现在,不管我们需要实例化多少个对象,都使用getInstance来实现
// // 创建遮罩层
// var createMask = function () {
// var div = document.createElement('div');
// div.className = "mask";
// div.innerHTML = "遮罩层";
// div.style.display = 'none';
// div.style.width = 800;
// div.style.height = 2400;
// div.style.background = 'green';
// document.body.appendChild(div);
// return div;
// }
// // 创建iframe
// var createIframe = function () {
// var iframe = document.createElement('iframe');
// document.body.appendChild(iframe);
// return iframe;
// };
// // 测试创建遮罩层
// var createSingleMask = getInstance(createMask);
// document.addEventListener('click', function () {
// var win = createSingleMask();
// win.style.display = 'block';
// });
// // 测试创建iframe
// var createSingleIframe = getInstance(createIframe);
// document.addEventListener('click', function () {
// var win = createSingleIframe();
// win.src = "https://www.zhihu.com/question/20028810/answer/23249246";
// });
// // DP4. [创建型] 模块模式
// // 1. 为类提供私有和共有封装的方法,通过此种方式,让一个对象拥有私有和共有的方法/变量,有效控制对外暴露的接口,屏蔽底层处理逻辑
// // 2. JS中没有访问修饰符,可以通过立即执行函数,闭包和函数作用域实现
// // 3. 实例
// var obj = (function () {
// var count = 0;
// return {
// addCount: function () {
// count++;
// },
// getCount: function () {
// return count;
// }
// };
// })();
// // 最终外部采用调用模块的公有属性/方法来访问和操作私有变量
// console.log(obj);
// obj.addCount();
// obj.getCount(); // 1
// obj.count // undefined
// // 4. 应用场景
// // 需要管理大量私有变量/方法,希望屏蔽内部处理逻辑,只对外暴露接口的独立模块
// // 5. 优点:
// // 采用封装的思想,只有本模块才能享有私有变量,不会暴露于外部模块
// // 减少对全局作用域的影响,避免命名空间污染;
// // 模块化有助于保持代码的整洁、隔离和条理性。
// // 5. 缺点:
// // 无法访问方法调用后产生的私有变量
// DP5. [创建型] 揭示模块模式(改进的模块模式)
// 1. 在模块模式基础上:在本模块的私有作用域范围内定义所有的方法和变量,将把返回对象的属性映射到我们想要公开的私有函数
// 2. 实例
// var obj = (function () {
// var count = 0;
// function addCount() {
// count++;
// }
// function getCount() {
// return count;
// }
// return {
// addCount, getCount,
// }
// })();
// console.log(obj);
// console.log(obj.addCount());
// console.log(obj.getCount());
// 3. 相比于模块模式优点:
// 方法从private改为public非常简单,只需要改属性映射
// 返回对象不包括任何函数定义,公开的函数和变量一目了然,有助于提高代码可读性
// 4. 相比于模块模式缺点:
// 导致私有函数升级困难。如果一个私有函数引用一个共有函数,在需要打补丁的时候,共有函数不能被覆盖
// function urlBuilder() {
// var _baseURL = "http://hao123.com";
// var _builder = function (relURL) {
// return _baseURL + relURL;
// }
// return {
// baseURL: _baseURL,
// build: _builder
// }
// }
// var builder = new urlBuilder();
// builder.baseURL = "http://stackoverflow.com";
// console.log(builder); // {baseURL: "http://stackoverflow.com", build: ƒ} builder的baseURL已经改变了
// console.log(builder.build("/question?id=2005"));
// // 期望: http://stackoverflow.com//question?id=2005
// // 实际: http://hao123.com//question?id=2005
// // 解决:加setXXX函数
// DP6. [创建型] 代理模式
// 6.1: 定义
// 代理是一个对象,跟本体对象具有相同的接口,以此达到对本体对象的访问控制
// 简言之,本体对象注重执行具体业务逻辑,而代理则控制着本体对象何时被实例化,何时被使用
// 6.2: 优点
// 代理对象可以代替本体被实例化,并使其可以被远程访问
// 代理模式可以延迟创建开销很大的本体对象,它会把本体的实例化推迟到有方法被调用时
// 6.3: 实例
// var girl = function (name) {
// this.name = name;
// }
// var boy = function (girl) {
// this.girl = girl;
// this.sendGift = function (gift) {
// console.log(`Hi, ${girl.name}, 男孩子送你一个礼物:${gift}`);
// }
// }
// var proxyObj = function (girl) {
// this.girl = girl;
// this.sendGift = function (gift) {
// (new boy(girl)).sendGift(gift);
// }
// };
// var proxy = new proxyObj(new girl("小洋葱"));
// proxy.sendGift('999朵玫瑰花');
// 6.4: 使用场景:图片加载
// 当图片过大或者网络较慢时,如果直接给img标签设置src属性,图片加载过程中有一段时间空白,降低了用户体验
// 传统方案:在图片未加载完成之前,使用一个loading图标作为占位符,等图片完成加载后,在使用真正的图片地址代替loading图标
// 不使用代理得预加载图片函数如下
// var myImage = (function () {
// var imgNode = document.createElement("img");
// document.body.appendChild(imgNode);
// var img = new Image();
// img.onload = function () {
// imgNode.src = this.src;
// };
// var setSrc = function (src) {
// imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
// img.src = src;
// }
// return { setSrc, }
// })();
// myImage.setSrc("https://www.baidu.com/img/bd_logo1.png");
// 耦合性太高,myImage函数违背了单一职责原则,同时完成了多个任务
// // 代理模式解决方案
// var myImage = (function () {
// var imgNode = document.createElement('img');
// document.body.appendChild(imgNode);
// var setSrc = function (src) {
// imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
// }
// return { setSrc, }
// })();
// var ProxyImage = (function () {
// var img = new Image();
// img.onload = function () {
// myImage.setSrc(this.src);
// }
// var setSrc = function (src) {
// myImage.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
// img.src = src;
// }
// return {
// setSrc,
// }
// })();
// ProxyImage.setSrc("https://www.baidu.com/img/bd_logo1.png");
// // 6.5: 缓存代理
// var mult = function () {
// var a = 1;
// const len = arguments.length;
// for (let i = 0; i < len; i++) {
// a *= arguments[i];
// }
// return a;
// }
// var plus = function () {
// var a = 0;
// const len = arguments.length;
// for (let i = 0; i < len; i++) {
// a += arguments[i];
// }
// return a;
// }
// var proxyFunc = function (func) {
// var cache = {};
// return function () {
// var args = Array.prototype.join.call(arguments, ',');
// if (args in cache) {
// return cache[args]
// }
// return cache[args] = func.apply(this, arguments);
// }
// }
// var proxyMult = proxyFunc(mult);
// console.log(proxyMult(1, 2, 3, 4)); // 24
// console.log(proxyMult(1, 2, 3, 4)); // 取cache
// var proxyPlus = proxyFunc(plus);
// console.log(proxyPlus(1, 2, 3, 4)); // 10
// DP7. [创建型] 职责链模式
// 7.1 职责链是由多个不同对象组成的,有发送者和接收者。发送者是发送请求的对象,接收者接受请求并对其处理或者传递
// 7.2 流程
// 1. 发送者知道链中的第一个接收者,它向这个接收者发送该请求
// 2. 每一个接收者都对请求进行分析,然后要么处理它,要么它往下传递
// 3. 每一个接收者知道其他的对象只有一个,及它在链中的下家
// 4. 如果没有任何接收者处理请求,那么请求会从链中离开
// 7.3 场景描述
// 以电商网站抽奖为例,规则如下:
// 1. 用户充值500元,可以100%中奖100元红包
// 2. 用户充值200元,可以100%中奖20元红包
// 3. 用户不充值,可以抽奖,但是中奖概率极低
// 4. 用户充值失败,按不充值处理
// 7.4 普通实现
// 1. orderType: 充值类型 1表示重置500元,2表示充值200元,3表示未充值
// 2. isPay: 是否充值成功 true表示充值成功,false表示充值失败
// 3. count: 表示数量 普通用户抽奖,如果数量有的话,就可以拿到优惠券。否则拿不到
// var order = function (orderType, isPay, count) {
// if (orderType === 1) {
// if (isPay) {
// console.log('亲爱的用户,您中奖了100元红包');
// } else {
// if (count > 0) {
// console.log('亲爱的用户,您已抽到10元优惠券');
// } else {
// console.log('亲爱的用户,请再接再厉呦');
// }
// }
// } else if (orderType === 2) {
// if (isPay) {
// console.log('亲爱的用户,您中奖了20元红包');
// } else {
// if (count > 0) {
// console.log('亲爱的用户,您已抽到10元优惠券');
// } else {
// console.log('亲爱的用户,请再接再厉呦');
// }
// }
// } else if (orderType === 3) {
// if (count > 0) {
// console.log('亲爱的用户,您已抽到10元优惠券');
// } else {
// console.log('亲爱的用户,请再接再厉呦');
// }
// }
// }
// // 以上代码实现了业务需求,但是存在以下问题:
// // 1. 业务逻辑代码耦合性太强,如果在增加条件,很容易改出问题
// // 2. 冗余代码太多,普通用户抽奖代码可以抽离
// // 7.5 职责链实现
// function order500(orderType, isPay, count) {
// if (orderType === 1 && isPay) {
// console.log('亲爱的用户,您中奖了100元红包');
// } else {
// // 我也不知道下一个节点是谁,反正把请求往后面传递
// return 'nextSuccessor';
// }
// }
// function order200(orderType, isPay, count) {
// if (orderType === 2 && isPay) {
// console.log('亲爱的用户,您中奖了20元红包');
// } else {
// // 我也不知道下一个节点是谁,反正把请求往后面传递
// return 'nextSuccessor';
// }
// }
// function orderNormal(orderType, isPay, count) {
// if (count > 0) {
// console.log('亲爱的用户,您已抽到10元优惠券');
// } else {
// console.log('亲爱的用户,请再接再厉呦');
// }
// }
// // 职责链模式构造函数
// var Chain = function (func) {
// this.func = func;
// this.successor = null;
// };
// Chain.prototype.setNextSuccessort = function (successor) {
// return this.successor = successor;
// }
// Chain.prototype.passRequest = function () {
// var ret = this.func.apply(this, arguments);
// if (ret === 'nextSuccessor') {
// return this.successor && this.successor.passRequest.apply(this.successor, arguments);
// }
// return ret;
// }
// // 创造职责链节点
// var chainOrder500 = new Chain(order500);
// var chainOrder200 = new Chain(order200);
// var chainOrderNormal = new Chain(orderNormal);
// // 指定职责链顺序
// chainOrder500.setNextSuccessort(chainOrder200);
// chainOrder200.setNextSuccessort(chainOrderNormal);
// // 把请求传递给第一个节点
// chainOrder500.passRequest(1, true, 500); // 亲爱的用户,您中奖了100元红包
// chainOrder500.passRequest(2, true, 500); // 亲爱的用户,您中奖了20元红包
// chainOrder500.passRequest(3, true, 500); // 亲爱的用户,您已抽到10元优惠券
// chainOrder500.passRequest(1, false, 0); // 亲爱的用户,请再接再厉呦
// 一、分别编写order500,order200,orderNormal三个函数,处理自己的业务逻辑,如果自己的函数不能处理的话,就返回字符串nextSuccessor 往后面传递
// 二、封装Chain构造函数,接收fn做为参数,且带有属性successor。实例化后的Chain类型的对象,就像一个一个独立存在的节点
// 三、Chain构造函数原型上有2个方法,分别是setNextSuccessor 和 passRequest。setNextSuccessor 方法指定了节点在职责链中的顺序,passRequest方法是将请求转移到职责链的下一个节点
// 7.6 优点
// 解耦了请求发送者和多个接收者之间的关系,无需知道链中哪个节点可以处理你的需求,只需把需求传递到第一个节点就好
// 链中节点可以灵活地拆分重组,增加或者删除一个和节点,都是很简单的事儿
// 可以手动指定起始节点
// 7.7 缺点
// 职责链很长的时候,大部分节点可能都没起到实质性作用,时间都耗费到传递请求上了
// DP8 策略模式
// 8.1 定义:策略模式定义一系列算法,把他们一个个封装起来,并且使它们可以相互替换
// 8.2 优点:
// 1. 可以有效避免很多if条件语句
// 2. 策略模式符合开放-封闭原则,代码跟容易看理解和扩展
// 3. 可复用
// 8.3 实战一
// 以公司年终奖为例:
// 一、绩效为A的人,年终奖为工资的4倍
// 二、绩效为B的人,年终奖为工资的3倍
// 三、绩效为C的人,年终奖为工资的2倍
// 传统实现
// var calculateBonus = function (salary, level) {
// if (level === 'A') {
// return salary * 4;
// }
// if (level === 'B') {
// return salary * 3;
// }
// if (level === 'C') {
// return salary * 2;
// }
// };
// console.log(calculateBonus(4000, "A")); // 16000
// console.log(calculateBonus(2500, "B")); // 7500
// console.log(calculateBonus(2000, "C")); // 4000
// 使用组合函数重构代码
// var performanceA = function (salary) {
// return salary * 4;
// }
// var performanceB = function (salary) {
// return salary * 3;
// }
// var performanceC = function (salary) {
// return salary * 2;
// }
// var calculateBonus = function (level, salary) {
// if (level === 'A') {
// return performanceA(salary);
// }
// if (level === 'B') {
// return performanceB(salary);
// }
// if (level === 'C') {
// return performanceC(salary);
// }
// };
// console.log(calculateBonus('A', 4500)); // 18000
// 使用策略模式重构代码
// 一个基于策略模式的程序至少由2部分组成,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程
// 第二个部分是环境类Context,该Context接收客户端的请求,随后把请求委托给某一个策略类
// var obj = {
// 'A': function (salary) {
// return salary * 4;
// },
// 'B': function (salary) {
// return salary * 3;
// },
// 'C': function (salary) {
// return salary * 2;
// },
// };
// var calculateBonus = function (level, salary) {
// return obj[level](salary);
// }
// console.log(calculateBouns('A', 10000)); // 40000
// 8.4 实战二: 表单检验
// var strategys = {
// // 非空
// isNotEmpty: function (value, errorMsg) {
// if (value === '') {
// return errorMsg;
// }
// },
// // 限制最小长度
// minLength: function (value, length, errorMsg) {
// if (value.length < length) {
// return errorMsg;
// }
// },
// // 手机号码格式
// mobileFormat: function (value, errorMsg) {
// if (!(/^1[3|5|8][0-9]{9}/.test(value))) {
// return errorMsg;
// }
// }
// };
// var Validator = function () {
// this.cache = {}; // 保存校验规则
// };
// Validator.prototype.add = function (dom, rules) {
// var self = this;
// for (var i = 0, rule; rule = rules[i++];) {
// (function (rule) {
// var strategyArr = rule.strategy.split(":");
// var errorMsg = rule.errorMsg;
// self.cache.push(function () {
// var strategy = strateggArr.shift();
// strategyArr.unshift(dom.value);
// strategyArr.push(errorMsg);
// return strategys[strategy].apply(dom, strategy);
// });
// })(rule);
// }
// }
// Validator.prototype.start = function () {
// for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
// var msg = validatorFunc();
// if (msg) {
// return msg;
// }
// }
// }
// var registerForm = document.getElementById("registerForm");
// var validateFunc = function () {
// var validator = new Validator(); // 创建一个Validator对象
// /* 添加一些效验规则 */
// validator.add(registerForm.userName, [
// { strategy: 'isNotEmpty', errorMsg: '用户名不能为空' },
// { strategy: 'minLength:6', errorMsg: '用户名长度不能小于6位' }
// ]);
// validator.add(registerForm.password, [
// { strategy: 'minLength:6', errorMsg: '密码长度不能小于6位' },
// ]);
// validator.add(registerForm.phoneNumber, [
// { strategy: 'mobileFormat', errorMsg: '手机号格式不正确' },
// ]);
// var errorMsg = validator.start(); // 获得效验结果
// return errorMsg; // 返回效验结果
// };
// // 点击确定提交
// registerForm.onsubmit = function () {
// var errorMsg = validateFunc();
// if (errorMsg) {
// alert(errorMsg);
// return false;
// }
// }
// DP8 发布-订阅模式(观察者模式)
// 8.1 定义:定义了对象间的一种一对多关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知
// 8.2 场景:
// 以网购为例:
// 假设小红看上了一双鞋子,但该鞋子已经断货了,卖家承诺她到货通知。与此同时,小明、小花灯也关注了这双鞋子。
// 在这个场景中,卖家就是发布者,小红等人都属于订阅者。当鞋子到货时,会依次通知到每个人。
// 8.2 优点
// 1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象
// 2. 发布者与订阅者耦合度降低,发布者只管发布消息,订阅者只管监听发布者的事件名
// 8.3 缺点
// 创建订阅者需要消耗一定的时间和内存
// 8.4 如何实现
// 首先想好谁是发布者,然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者,最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数
// 8.5 代码
// var Event = (function () {
// var list = {},
// listen,
// trigger,
// remove;
// listen = function (key, fn) {
// if (!list[key]) {
// // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
// list[key] = [];
// }
// list[key].push(fn); // 订阅消息添加到缓存列表
// };
// trigger = function () {
// var key = Array.prototype.shift.call(arguments), // 取出消息类型名称
// fns = list[key]; // 取出该消息对应的回调函数的集合
// // 如果没有订阅过该消息的话,则返回
// if (!fns || fns.length === 0) {
// return false;
// }
// for (var i = 0, fn; fn = fns[i++];) {
// fn.apply(this, arguments); // arguments 是发布消息时附送的参数
// }
// };
// remove = function (key, fn) {
// // 如果key对应的消息没有订阅过的话,则返回
// var fns = list[key];
// // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
// if (!fns) {
// return false;
// }
// if (!fn) {
// fns && (fns.length = 0);
// } else {
// for (var i = fns.length - 1; i >= 0; i--) {
// var _fn = fns[i];
// if (_fn === fn) {
// fns.splice(i, 1);// 删除订阅者的回调函数
// }
// }
// }
// };
// return {
// listen,
// trigger,
// remove,
// }
// })();
// // 测试代码如下:
// Event.listen("size", function (name) {
// console.log("亲爱的用户" + name + "你好,42号尺码的鞋子到货了.");
// });
// Event.trigger("size", ['王杰', '小明', '王炯', '刘涛']);
// // 亲爱的用户王杰,小明,王炯,刘涛你好,42号尺码的鞋子到货了.
转载自:https://juejin.im/post/5c0e6ab96fb9a049f43b26da