行为型设计模式(二)

  • 命令模式(Command)
    • 核心
    • 示例一:自由化创建视图
    • 示例二:绘图命令
  • 访问者模式(Visitor)
    • 核心
    • 示例一:IE 兼容
    • 示例二:操作类数组对象
  • 中介者(Mediator)
    • 核心
    • 示例一:导航设置功能
  • 备忘录模式(Memento)
    • 核心
    • 示例一:网络请求数据的缓存
  • 迭代器模式(Iterator)
    • 核心
    • 示例一:简化循环遍历
    • 示例二:数组/对象迭代器
    • 示例三:同步变量迭代器
    • 示例四:分支循环嵌套问题
  • 解释器模式(Iterpreter)
    • 核心
    • 示例一:统计元素路径
  • 用于不同 对象间 职责划分或算法抽象
  • 代码:https://github.com/baixc1/csdn/tree/master/DesignPatterns/Behavior

命令模式(Command)

核心

  • 定义:将请求与实现解耦,封装成独立对象。使不同的请求对客户端的实现 参数化。
  • 重点:简化使用方法,以参数配置的形式,实现复杂业务。重点是封装底层实现部分。
  • 应用:组件,库,框架,编程中大量使用。调用和实现的解耦,上层调用者不用关心具体实现,加快业务开发
  • 举例:以参数配置的形式,动态生成UI
viewCommand({cmd: "display",params: ["titleId", titleData, "title"], // display 参数
})({cmd: "create",params: [productData, "product"], // create参数,创建3个列表项
})({cmd: "display",params: [// 创建1个列表项,并展示"productId",{src: "4.jpg",text: "text4",},"product",],
});

示例一:自由化创建视图

  • 需求:自由创建视图(标题,单张图片,多张图片。。。)
  • js
// 模块实现模块
var viewCommand = (function () {// 模板(变量:{#var#})var tpl = {product: `<div><img src="{#src#}" /><p>{#text#}</p></div>`,title: `<div class="title"><div class="main"><h2>{#title#}</h2><p>{#tips#}</p></div></div>`,};// 方法集合(通过key调用)var Action = {create(data, view) {// 数组if (data.length) {for (let v of data) {html += formatString(tpl[view], v);}} else {html += formatString(tpl[view], data);}return this; // 链式调用},display(container, data, view) {if (data) {this.create(data, view);}document.getElementById(container).innerHTML = html;html = "";return this; // 链式调用},};// 当前的 格式化字符串(可用作列表缓存数据拼接)var html = "";/*** 命令接口* cmd: Action 命令* params: 模版及其变量值,[data, view]*/return function excute({ cmd, params }) {// 调用时,this绑定为Action(apply第二个参数为数组)Action[cmd].apply(Action, Array.isArray(params) ? params : [params]);return excute;};/*** 生成模版字符串* @function formatString* @param {string} str - 模版字符串* @param {object} obj - 变量对象* @return {string}  - 替换变量后的模版字符串*/function formatString(str, obj) {return str.replace(/\{#(\w+)#\}/g, function (match, key) {return obj[key];});}
})();var productData = [{src: "1.jpg",text: "text1",},{src: "2.jpg",text: "text2",},{src: "3.jpg",text: "text3",},
];var titleData = {title: "夏日里的一片温馨",tips: "暖暖的温情带给人们家的感受",
};viewCommand({cmd: "display",params: ["titleId", titleData, "title"], // display 参数
})({cmd: "create",params: [productData, "product"], // create参数,创建3个列表项
})({cmd: "display",params: [// 创建1个列表项,并展示"productId",{src: "4.jpg",text: "text4",},"product",],
});
  • 引用
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>#productId {display: flex;}#productId > div {margin-right: 20px;}</style></head><body><div id="titleId"></div>------------------------<div id="productId"></div><script src="./index.js"></script></body>
</html>
  • 效果

示例二:绘图命令

  • 需求:使用canvas绘图时,将上下文对象封装在命令对象内部(可统一解决兼容性问题)
  • js
// index2.js
// 绘图命令
// 功能:用于解耦,封装上下文对象function getType(v) {return Object.prototype.toString.call(v);
}
// 绘图对象
var CanvasCmd = function (id) {var canvas = document.getElementById(id);var ctx = canvas.getContext("2d");return {excute(msg) {if (!msg) return;// 处理命令数组if (msg.length) {for (let v of msg) {arguments.callee(v);}}// 处理每条命令else {let { param, cmd } = msg;// canvas绘图 没有该 apiif (ctx[cmd] === undefined) return;// 命令是函数if (typeof ctx[cmd] === "function") {ctx[cmd](...param);} else {ctx[cmd] = param;}}},};
};CanvasCmd("canvas").excute([{cmd: "fillStyle",param: "red",},{cmd: "fillRect",param: [20, 20, 100, 100],},
]);
  • 调用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><canvas id="canvas" width="300" height="300">抱歉,您的浏览器不支持canvas元素(这些内容将会在不支持<canvas>元素的浏览器或是禁用了JavaScript的浏览器内渲染并展现)</canvas><script src="./index2.js"></script>
</body>
</html>
  • 效果

访问者模式(Visitor)

核心

  • 定义:为对象提供统一的、兼容的新的访问方法
  • 重点:二次封装对象的访问方法
  • 应用:IE 相关 api 的兼容,操作类数组对象

示例一:IE 兼容

// 问题:兼容访问api
// 在IE 9之前,必须使用 attachEvent
document.getElementById("id").attachEvent("onclick", function () {// 使用 attachEvent 方法有个缺点,this 的值会变成 window 对象的引用而不是触发事件的元素。console.log(this);
});// 显示:使用call绑定this,并传入自定义数据
function bindIEEvent(dom, type, fn, data = {}) {dom.attachEvent("on" + type, function (e) {fn.call(dom, e, data);});
}

示例二:操作类数组对象

  • 对象原型添加数组方法
// index3.js
// 扩展对象原型:新增数组方法(类数组对象)
// arguments除了length属性和索引元素之外没有任何Array属性
Object.prototype = Object.assign(Object.prototype, {splice() {// const params = Array.prototype.slice.call(arguments)// const params = [...arguments]// const params = Array.from(arguments);// this指向对象实例,不需转化arguments为数组,自动添加length属性return Array.prototype.splice.apply(this, arguments);},push() {// this.length += (this.length || 0) + arguments.length;return Array.prototype.push.apply(this, arguments);},pop() {return Array.prototype.pop.apply(this);},
});const obj = {};
console.log(obj); // {}
console.log(obj.push(1, 2, 3, 4)); // 4
console.log(obj); // { '0': 1, '1': 2, '2': 3, '3': 4, length: 4 }
console.log(obj.splice(1, 1, 6, 7, 8)); // [ 2 ]
console.log(obj); // { '0': 1, '1': 6, '2': 7, '3': 8, '4': 3, '5': 4, length: 6 }
console.log(obj.pop()); // 4
console.log(obj); // { '0': 1, '1': 6, '2': 7, '3': 8, '4': 3, length: 5 }
console.log(Object.prototype);

*其他测试

// index2.js
// 原生对象构造器:访问者(内部访问了this)
const toString = Object.prototype.toString;
console.log(toString.apply({})); // [object Object]
console.log(toString.call(1)); // [object Number]
console.log("------------");
// 其他测试
console.log({}.__proto__.toString === toString); // true
console.log("toString" in {}); // true
console.log({}.hasOwnProperty("toString")); // false

中介者(Mediator)

核心

  • 定义:通过中介者对象(消息容器),封装对象间的交互,减少对象间的耦合
  • 重点:多个模块的统一设置功能
  • 应用:页面设置功能(供用户操作页面元素的显示/隐藏。。。)(解决模块间通信问题)

示例一:导航设置功能

  • 需求:通过导航设置层,统一控制页面中多个导航的元素显示方法
  • 导航组件封装
// index2.js
// 格式化字符串
function formatString(str, data) {// 全局匹配 {#xxx#} -> xxx(数字,字母,下划线)return str.replace(/\{#(\w+)#\}/g, function (match, key) {return (data && data[key]) || "";});
}// 基础导航
var Nav = function (data) {// 模板this.item = '<a href="{#href#}" title="{#title#}">{#name#}</a>';this.html = "";for (const v of data) {this.html += formatString(this.item, v);}return this.html;
};// 信息导航
var NumNav = function (data) {// 模板var tpl = "<b>{#num#}</b>";for (let i = 0; i < data.length; i++) {data[i].name += formatString(tpl, data[i]);}return Nav.call(this, data);
};// 网址导航
var LinkNav = function (data) {var tpl = "<span>{#link#}</span>";for (let i = 0; i < data.length; i++) {data[i].name += formatString(tpl, data[i]);}return Nav.call(this, data);
};// 网址/消息导航
var LinkNumNav = function (data) {var tpl = "<span>{#link#}</span>";for (let i = 0; i < data.length; i++) {data[i].name += formatString(tpl, data[i]);}return NumNav.call(this, data);
};
  • 中介者对象(消息容器)
// 中介者对象(消息系统)
var Mediator = (function () {// 消息对象var msg = {};return {/*** 注册消息方法* @param {string} type* @param {function} action*/register(type, action) {if (!msg[type]) {msg[type] = [];}msg[type].push(action);return this;},// 发布消息emit(type) {if (!msg[type]) return;for (let fn of msg[type]) {fn && fn();}},};
})();/*** 显隐导航组件* @param {dom} ele* @param {string} tag 标签* @param {boolean} isShow*/
var showHideNav = function (ele, tag, isShow) {var subEle = ele.getElementsByTagName(tag);var display = isShow ? "initial" : "none";for (let v of subEle) {v.style.display = display;}
};// Mediator.register("demo", () => console.log("first"));
// Mediator.register("demo", () => console.log("second"));// Mediator.emit("demo");

*页面调用

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>a {margin-right: 6px;display: block;}.mu {margin-bottom: 50px;}</style>
</head><body><div>用户收藏导航模块(网址+消息)</div><div class="mu"></div><div>推荐用户导航模块(消息)</div><div class="mu"></div><div>最近常用导航模块(网址)</div><div class="mu"></div><div>用户收藏导航模块(网址+消息)</div><div class="mu"></div><input type="checkbox" id="hide_num">隐藏消息</input><input type="checkbox" id="hide_url">隐藏链接</input><script src="./index.js"></script><script src="./index2.js"></script><script>// 导航数据const list = [{href: "http://www.baidu.com",title: "面板",name: "tab1",type: 'LinkNumNav'},{href: "http://www.baidu.com",title: "面板",name: "tab2",type: 'NumNav'},{href: "http://www.baidu.com",title: "面板",name: "tab3",type: 'LinkNav'},{href: "http://www.baidu.com",title: "面板",name: "tab4",type: 'LinkNumNav'},];// 导航模块(主体列表)(() => {[...document.getElementsByClassName('mu')].forEach((item, index) => {const { type } = list[index]const hasNum = ['LinkNumNav', 'NumNav'].includes(type)const hasLink = ['LinkNumNav', 'LinkNav'].includes(type)// 导航元素显示item.innerHTML = window[type](list.map((item, index) => {const obj = { ...item }// 5-10随机数const r1 = Math.floor(Math.random() * 5) + 5// 97 - 97+26(ascii字母)let path = ''for (let i = 0; i < r1; i++) {path += String.fromCharCode(Math.floor(Math.random() * 26) + 97)console.log(path)}if (hasNum) {obj.num = `(消息:${r1})`}if (hasLink) {obj.link = `(网址:http://xiaoxi.www/${path}.com`}return obj}))// 注册消息事件if (hasNum) {Mediator.register('hideAllNavNum', function () {showHideNav(item, 'b', false)}).register('showAllNavNum', function () {showHideNav(item, 'b', true)})}// 注册链接事件if (hasLink) {Mediator.register('hideAllNavUrl', function () {showHideNav(item, 'span', false)}).register('showAllNavUrl', function () {showHideNav(item, 'span', true)})}});})();// 设置层模块(底部多选框)(() => {var hideNum = document.getElementById("hide_num");var hideUrl = document.getElementById("hide_url");[hideNum, hideUrl].forEach((item, index) => {item.onchange = function () {const str = index === 0 ? "Num" : "Url";if (item.checked) {Mediator.emit("hideAllNav" + str);} else {Mediator.emit("showAllNav" + str);}};});})();</script>
</body></html>
  • 效果

备忘录模式(Memento)

核心

  • 定义:在不破坏对象封装性的前提下,在对象外缓存对象的状态,以便重复使用
  • 应用:网络请求数据设置有效期缓存(不同场景的缓存模式),MVC中 Model 层部分

示例一:网络请求数据的缓存

// 缓存数据(备忘录模式)
var Page = (function () {// 缓存对象var cache = {};// 获取数据(缓存或网络请求)return function (page) {if (cache[page]) {return Promise.resolve(cache[page]);} else {return new Promise((resolve, reject) => {// 模拟ajax请求setTimeout(() => {const res = { list: [], total: 0, page };cache[page] = res;resolve(res);}, 1000);});}};
})();// 测试代码
(async () => {let data = await Page(1);console.log(data); // 1s后获取数据(模拟网络请求)data = await Page(1);console.log(data); // 立即获取数据(缓存)data = await Page(2);console.log(data); // 1s后获取数据(模拟网络请求)
})();

迭代器模式(Iterator)

核心

  • 定义:在不暴露对象内部结构的同时,提供顺序访问(聚合对象内部)元素的方法
  • 重点:优化大量重复循环导致的代码臃肿问题
  • 应用:重复循环逻辑的封装提取,定制化的对象/数组迭代器

示例一:简化循环遍历

  • 需求:不同种类的焦点图(轮播图),封装其访问/操作元素的部分(通过迭代器控制元素及其访问)
  • js
// Iterator.js
/*** 迭代器(简化遍历/访问操作)* @param {string} containerId - 容器元素id* @param {string} subTag - 容器子元素 tag* @returns*/
var Iterator = function (containerId, subTag) {const container = document.getElementById(containerId);const items = container.getElementsByTagName(subTag);const len = items.length;let index = 0; // 当前访问的元素的索引return {// 获取第一个元素first() {index = 0;return items[index];},// 获取最后一个元素last() {index = len - 1;return items[index];},// 上一个(负数取第一个)pre() {if (--index > 0) {return items[index];} else {index = 0;return null;}},// 下一个next() {if (++index < length) {return items[index];} else {index = length - 1;return null;}},// 获取第 n 个元素(负数和超过len时,转化为0 -> len-1)get(num) {index = num > 0 ? num % length : (num % length) + length;},// 处理每个元素, 回调函数 + 参数dealEach(fn, ...params) {[...items].forEach((item) => {fn.apply(item, params);});},// 处理某个元素,元素下标 + 回调函数 + 参数dealItem(num, fn, ...params) {fn.apply(items[num], params);},// 排他方式处理元素,元素下标(number/array) + 处理全部元素的回调 + 处理num元素的回调exclusive(num, allFn, numFn) {this.dealEach(allFn);if (Array.isArray(num)) {for (let v of num) {this.dealItem(v, numFn);}} else {this.dealItem(num, numFn);}},};
};

*html

<!--Iterator.html-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./Iterator.js"></script>
</head><body><ul id="container"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><script>var demo = new Iterator('container', 'li')console.log(demo.first())console.log(demo.last())console.log(demo.pre())console.log(demo.get(-10))console.log(demo.next(-10))// 处理每个元素demo.dealEach(function (text, color) {this.innerText += textthis.style.color = color}, '-test', 'pink')// 排他方式处理元素demo.exclusive([1, 2], function () {this.innerText += '-被排除的'}, function () {this.innerText += '-被选中的'})</script>
</body></html>
  • 效果

示例二:数组/对象迭代器

  • 需求:数组相关原型方法(forEach, some)的实现
  • js
// ArrayAndObjectIterator.js
// 数组原型方法 forEach
// arr.forEach(callback(currentValue [, index [, array]])[, thisArg])Array.prototype.forEach = function (callback, thisArg) {var len = this.length;var i = 0;for (; i < len; i++) {callback.call(thisArg, this[i], i, this);}
};// arr.some(callback(element[, index[, array]])[, thisArg])
// 数组中有至少一个元素通过回调函数的测试就会返回true;所有元素都没有通过回调函数的测试返回值才会为false。
Array.prototype.some = function (callback, thisArg) {var len = this.length;var i = 0;for (; i < len; i++) {if (callback.call(thisArg, this[i], i, this)) {return true;}}return false;
};Object.prototype.forEach = function (callback, thisArg) {for (var key in this) {if (!this.hasOwnProperty(key)) continue; // 排除forEachcallback.call(thisArg, this[key], key, this);}
};console.log("forEach",[1, 2, 3].forEach((item, index, arr) => {console.log("forEachItem", item, index, arr);if (index === 1) return true;})
);
console.log("some",[1, 2, 3].some((item, index, arr) => {console.log("someItem", item, index, arr);if (index === 1) return true;})
);
console.log("obj-forEach",{ a: 1, b: 2 }.forEach((item, key, obj) => {console.log("obj-forEachItem", item, key, obj);})
);

示例三:同步变量迭代器

  • 需求:设置/访问对象属性,处理 undefined 的情况(对象的链式访问 - 可选链)
  • js
ViaIterator = {/*** 变量链式迭代取值器* @param {string} key - 链式可以, 例: 'a.b.c'* @param {object} obj* @returns*/get(obj, key) {let ret = obj; // 对象指针,指向访问的对象层级var keys = key.split("."); // key 的数组for (let v of keys) {if (!ret) return;if (ret[v] !== undefined) {ret = ret[v];}}return ret;},// 变量链式迭代赋值器set(obj, key, val) {let ret = obj; // 对象指针,指向访问的对象层级var keys = key.split("."); // key 的数组for (var i = 0, len = keys.length; i < len - 1; i++) {const v = keys[i];if (ret[v] === undefined) {ret[v] = {};}if (!(ret[v] instanceof Object)) {throw new Error(`obj.${keys.slice(0, i + 1).join(".")}不是对象`);}ret = ret[v];}return (ret[keys[i]] = val);},
};console.log(ViaIterator.get({ a: null }, "a.b.c")); // undefined
console.log(ViaIterator.get({ a: { b: { c: null } } }, "a.b.c")); // undefinedvar obj = {};
console.log(ViaIterator.set(obj, "a.b", { c: 1 })); // { c: 1 }
console.log(obj); // { a: { b: { c: 1 } } }
try {ViaIterator.set(obj, "a.b.c.val", "d");
} catch (e) {console.dir(e.message); // 'obj.a.b.c不是对象'
}

示例四:分支循环嵌套问题

  • 需求:canvas 处理图片像素数据,数据量大时,分支会影响性能
  • js
// canvas.js
// 分支循环嵌套问题// canvas 处理图片像素window.onload = function () {var canvas = document.getElementsByTagName("canvas")[0];var ctx = canvas.getContext("2d");var img = document.images[0];var width = (canvas.width = img.width * 2) / 2;var height = (canvas.height = img.height);// canvas左侧图片ctx.drawImage(img, 0, 0);// canvas置灰(蒙层1)dealImageYh("gray", 0, 0, width, height, 0);// canvas红色矩形(蒙层2)dealImageYh("red", 100, 100, 200, 200, 100);// canvas蓝色矩形(蒙层3)dealImageYh("blue", 120, 120, 160, 160, 255);/*** 绘制特效图片(未简化)* @param {string} t 类型* @param {*} x x坐标* @param {*} y* @param {*} w 宽* @param {*} h 高* @param {*} a 透明度*/function dealImage(t, x, y, w, h, a) {// 画布数据var canvasData = ctx.getImageData(x, y, w, h);// 像素数据var data = canvasData.data;// 遍历(rgba)for (var i = 0, len = data.length; i < len; i += 4) {data[i + 3] = a;switch (t) {case "red":data[i + 1] = data[i + 2] = 0;break;case "green":data[i] = data[i + 2] = 0;break;case "blue":data[i] = data[i + 1] = 0;break;case "gray":var num = parseInt((data[i] + data[i + 1] + data[i + 2]) / 3);data[i] = data[i + 1] = data[i + 2] = data[i + 3] = num;break;// ...}}ctx.putImageData(canvasData, width + x, y);}// 绘制特效图片(简化分支逻辑,优化性能)function dealImageYh(t, x, y, w, h, a) {var canvasData = ctx.getImageData(x, y, w, h);var data = canvasData.data;// 状态模式,简化分支var Deal = (function () {var methods = {default(i) {return methods.gray(i);},red(i) {data[i + 3] = a;data[i + 1] = data[i + 2] = 0;},green(i) {data[i + 3] = a;data[i] = data[i + 2] = 0;},blue(i) {data[i + 3] = a;data[i] = data[i + 1] = 0;},gray(i) {data[i + 3] = a;var num = parseInt((data[i] + data[i + 1] + data[i + 2]) / 3);data[i] = data[i + 1] = data[i + 2] = data[i + 3] = num;},};return function (type = "default") {return methods[type];};})();for (var i = 0, len = data.length; i < len; i += 4) {Deal(t)(i);}ctx.putImageData(canvasData, width + x, y);}
};

*html

<!--canvas.html-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./canvas.js"></script>
</head><body><canvas></canvas><!--base64图片,解决getImageData跨域问题--><img style="visibility: hidden;"src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAGmAdYDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAorkvEfxL8J+F9yahq0TXC/8u9v+8k+mBwPxIry7Wv2kQCyaJomR0Et3J+u1f8AGgD36ivkjUfjn45vywTUIbRD/DbwKMfQnJ/WufuPiN4yuTmTxLqQJ/uTlP5YoA+16K+H08b+KUlEq+INR3j+I3DH+tbFn8XfHNkCE1+eQYxiZVf+YoA+uNS17SNGCnVNTs7IPwpuZ1jz+Zqax1Kx1O3+0afe293DnHmW8qyL+YJFfCuo6nfaveyXmoXUtzcSHLSSsWJrQ8NeLdZ8Jait7pF48LA/PGTlJB6MvQ0AfclFcD8Ofijpnju18httpq8a5ltWbhx3ZD3Ht1H6131ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFU9T1bT9Gs3vNSvIbW3XrJK20f/XNZXjLxhpngrQZNT1F8n7sMKn5pn7KP6ntXyJ4w8a6x411Z73Up28sE+TbqT5cS+gH9aAPonUvj74NsjKtu15eOoO3yosKx9Mk8D3xXi/i/wCMvifxS0kMU502wbgW9sxBI/2m6n+Ved0UAKzFiWYkk9SaVUZ2CopZj0AGSa9D+F3hzwj4lv2s9bubhdQzmC33BI5h6Buu72719A6T4X0LQlC6bpNrbkfxiMFvzPNYVMQoO1jWFFzVz5f0r4feKtZ2m00a5CH+OVfLX/x7FdrpvwD1ycKdQ1KztAf4UBlb+gr6ByfWsfxVfXWmeEdWvrJc3MFqzx4GcH1/Ac1zvEzk7LQ39hGKuz5o8deH9E8L6guk6dqE19exf8fUrABEP9wY6n1rkqfLI80ryyOXkclmYnJJNbPhfwpqni3VVsdNhLd5ZW+5Evqx/pXavdjqzk3ehh4or6y8I+ANF8IWIjgt47m8YfvruZAWY+gz0Fee/FL4VBhN4g8O2/Iy91Zxj83QfzFYxxMZSsaujJRueMWF/daXfQ3tjO8FzCweOSM4KkV9W/Cz4pW/jexFjfFIdbgX95GOBOB/Go/mO30r5KxWjoE1/B4g099LuGgvvPRYZFONrEgD8K6DE+7aK5bwZ4pk1yC4sNTiFrrunkR3tv2PpIvqrdRXU0J3AKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKz9c1qx8PaPc6pqMwitrddzE9T6AepNXyQBknAr5S+M/wAQpPFXiB9JsZT/AGRYOVXaeJpBwXPsOg/E96AOX8eeNr/xz4gk1C6JS3TK21vniJP8T3NctRXS+CfB154z16Oxt8pbp89zPjiJP8T2FJtJXY0ruyObwcZ7UlfWd/8ADrw3qHhiPQjYJFbwriGdABKjf3t3cnuDXz54x+HOueELljPA1xYE/u7uJSUI9/7p9jWVOvGehpOlKJyUUskEqSxOySIwZWU4II7ivqT4YeMJfF/hbzbzm/tHEM7D/lpx8r/Ujr718sqpZgqgknoAK+l/g74Xu/DvhSSe/jMVzqEglETdUjAwuR2J5P5VGKS5NdysPfm0PRKayK6MjqGRgVZWGQQeoNOorzztPLL74E+H7rUXuIL+7tYHbd5CKGC+wJ7V3WnaZongvQGjtlisdPt13yyueT/tM3c1sMyqpZmCqoyzE4AHcmuEtrc/ETVP7RvA3/CLWUpFlbHgX0inmV/VAegrXmlNe89EZcsYv3VqSwa/4q8Ugz+G7K107Ss4jvtSUl5vdIx2+tSmD4k2g3x6hoOoEf8ALN4WhLe2a7HgAAAAAYAAwAPQUVPP2Q+TuzwrxB4O0nxLrghuLaTwn4gnP+ouF3Wl03rG44BPpXTeCfg3beGdVj1TVL1by7g5hijXCI39456kdq9D1XSbDXNOksNSt1ntn/hbqp9VPYj1Fc1pOoah4Z1mDw3rlw11aXGRpWpP958f8sZD/fA6HvWvtZSjZMj2ajK7LfirT7u1nt/FOjITq2mKS8a/8vVv/HGfU45HvXeaPqtrrmkWup2T77e5jEiHvz2PuOlZQJBz3rC8HuPD3i3UvDX3bO7B1DT17KCcSIPo3P41rhan2GZ4iFveR6BRRRXYcwUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFNd1RGdyFVRkkngCgDzb40+Nv8AhFPCRs7WXbqWpboosHlE/jb9QPx9q+TD1rr/AImeK38X+Nry+Dk2sR8i2X0jU/1OT+Nem+DPg7oVz4VtrvWhLcXl4izAxyFBEp6KPU+uaipUjBXZcIObsjxPRNEvvEOrwabp0JluJmwB2UdyT2Ar6Ksf7P8AhxpVt4a0S0Oq+Ibld7Qx8F2/vyH+FB2qS8s9J+HWnRWPhfTUbXNUfybVXO52Pd2J6IvWt/wv4Yh8OWkjPKbvVLo+ZfXz8vM/oD2UdhXJVq8yv0N6dNp26mRH4L1PWP8ASPFXiG8llbn7Hpshggi9sjlqdJ8PY7dWOj+INWsXI+5NL9phb2ZHzkV2VFc/tJG/s4nnmgQadovidNL8Q6BpVpq82WstRt4v3N3jsoP3H9q9E5J561k+IvD9n4m0eTT7wFcnfDMv34ZB911PYisvwlrt7LNP4e14BddsFBZ/4buLosq+vv705e8rij7rsdVRRRWZocl48nuLqzsPDdk7Jda1P5LuvWOBeZG/Lj8apaz8SPCHgyOPSYpjO9ogiW2tF3CMAYwW6A/rXA/GHxkIPFEdpo88kd7a2z21xcI3RXOSq+h9T+FeOEkkk8k967adBSiuY5J1WpOx7u/7QNgH/d6DcFP9qYA1v6D8Z/DGszpb3PnabKxwDcYKE/7w6fjXzYYJhF5hicJ/eKnH51HWrw1NohV5n22rB1DKwZWGQQcgiszxFocHiLQ7jTZ2KM43wzD70Mo5RwexBryL4KeOJjcDwrqMxeNgWsXc8qRyY/oR09+O9e41wzg6crHXGSqRMrw5Pqk+g2ra1bmDUUBjnGQQ5XjeMdm6/jWb408ywttP8R24Pn6PciV8fxQN8sg/Ln8K6eo54Irm3kt541khlUo6MMhlPUGlGfLLmQShePKRX3xF8PWjJFb3Emo3TqGW3sIzM5yMjO3gfjVD/hN/EU5DWvgi+EZ/5+bmONv++ck1e07TLHSLUW2m2cNpCP4YUC5+p6mrWK6JYt/ZRisMurMkePNWthuv/Berog6tbFJsfgCD+lauj+O/D2tT/ZoL4Q3fe2uVMUg/4C2P0pelZ+raHpeuQeVqdjDcL1VmXDqfUMOQaI4t/aQpYbszs6K8wXUdb+H7CSWa41rwyD+88z5rqyHrn/lon15Fej2V9balYw3tnMk9tMoeORDkMp7iuuM1JXRzyi4uzLFFFFWSFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXn/wAZPE//AAjfw+vPKfbd33+iw+vzfeP4LmvQK+YP2hPEB1Dxhb6PE+YdPhBcA8eY3P8ALFAHj9fZfh5dvhvSl6f6JF/6AK+NK+ydGUT+FtPj3ECSyRMr1AKYyK5MXsjpw+7Oe8JoPEPibV/Fko3xiVrDTR1CQx8Mw/3mzUPiv4reHvC8j2qudQv14aC3YbUPoz9M+wzXBfETxkvhWwj8C+GJmiitE8u6uQ3zknkoD+PJrxskkknknvShQU/elsKVXlVkep3/AMePEc8xNlZ2FrH2BQyN+ZOP0qfTfj3rcMijUtOtLqPPzeWPLbHtjivO9P8AC+v6tB59ho19cw/89I4GK/njFZ1za3FlcNb3UEsEyHDRyoVYfUGt/Y09rGftZ3vc+tfCfjLSPGVg1xpspEsf+utpOJI/qO49xW6YIWuEuGhjM6KUSUqNyqeoB64NfH3hnxDe+GNettUspCrxMN654de6n2Ir69sbyHUdPtr63OYbiJZU+jDNcVel7N6bHVSqc61J6bJKIIZJz0iRn/IE/wBKfUc8P2m2mgzjzY2TP1BFYGr2PjLU7p77Vbu6kYs80zOSe+TXqXwN8AWPinUrrV9VjWaysGVUgbpJIefm9QB2ryzUrSWw1O6tJ1KSwysjKR0INdP4D+I+seAbmY2IjntJyDNbS/dYjoQexr2FseYz7HWztVtfsq28Qt8bfKCALj0x0r5Q+Nvh/R/D3jrydIRIUngWaW3T7sbEkcDtnGcVuaz+0V4gvLYw6Zp1pYMwwZiTIw+mePzBryO/v7vVL6a9vriS4uZm3SSyNlmNMCfQ7yTT9e0+8iYq8Nwjgjrwwr7Mzk5xjPOK+Uvhv4Yn8TeMbKJYybS3kWa5fHCopzj6nGK+rScsT61w4trmSOvDJ2YUUUVyHSFFFFABRRRQAhAIIIBBGCCMg1yukSnwF4oi08HHhrWJSLcE8WdyeSg9Fft711dZviDR017QbzTWO1pk/dOODHIOUYH1BArWlUcJXM6sOaJ2nelrnPAuuSa/4Ssru4G28QGC6X0lQ7W/UZ/Gujr1DzwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArF8T+J7DwppDX98WYltkMEYy80h6Ko7mtqvLrP/irfH2oa7cfvNP0dzZach5Uy/8ALST69qipNQjzMqEeZ2GDTPFHi0/avEmpzaVZPzHpWnvtYKenmSdSenA6Vka78F/DmqW8jWct1aXpHEzymQMf9oHrXpGcmivOdebd7naqMErWPjfxBoN/4a1ifS9Ri8ueI9R0dezA9wa+q9EvBa+ArC9PPkaYkhHusY/wrlfjH4VTXPCT6pDEDfaYPMDActF/Ev4fe/A10HhOH+0vhlplsWx9p0sRFvQsmM/nWtWoqkE2ZwhySaPlK9upb2+nup3LyzSM7sepJOa7/wCC/hGy8WeMyNSQS2llH57QnpIc4APtmuB1Cyn07ULiyuY2jngkaN0Ycgg1o+F/FWq+ENYXU9JmEcwG1lYZV19CK70cjPuKKGK3iWKGNY41GFVBgAfSvCv2j9O0xdO0rUAsaam0xjyPvPHjPP0OOa5a4/aI8Vy23lw2mnwyf89BGW/QnFeZ65r+qeI9Qa+1a8lurhuNznhR6AdhQBm19b/D1ZE+HuhLLnf9lHX0ycfpivnDwN4LvvGWuR20KMlnGwa6uMcRr6fU9hX1hbwRWttDbQKEhhQRovoAMCuPFSWkTqw8Xe5JRRRXEdR5Z8SvhS3ie7bWdEaKPUWH7+FztWbH8QPZv514vf8AgbxPps3lXWiXitnAKx7gfxGa+u6UMw6E10QxEoq25hOhGTufIlr4D8VXhxBoN8f96Ir/ADxXYeH/AIHeIL+dH1d4tOtc/Nk75CPQAf1r6LLMerE/jSU5Yqb2EsPHqZPh7w5pfhfTF0/SrcRRDl3PLyN6se5rWoornbbd2dCSSsgooopAFFFABPQE0AFFHI6iigAoBwcjtRQelAGR4FxaeI/FmmrgIl6twijsJEB/nXdVwvhFN3j/AMWzDp/o0RPbIT/69d1XrU/gR5s/iYUUUVZIUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAGR4o1UaJ4W1PUicfZrZ3B98cfrXKeCtP8A7M8G6ZAw/eyR/aJT3Lv8xP61P8Wpj/whgsV6393DbY9QXGf5VriNYlWJRhUUIPoBiuPFvRI6cMtWxaKKK4jrGSRpNE8Uqh43UqynoQeCKr6bp1rpGmwafYxmO1gXbGhbJAznrVuinfoFlucB4/8AhdY+MnN/bTLZasFwZCMpNjpux3968Yv/AIT+MrCYp/ZLzqDgPAwdT+VfU1AJHQkfStoYicFYxnQjJ3PlW2+FfjS6bC6FcRj+9KQg/Mmu08O/Aa8eZJvEN9HBCOWgtzudvbPQV7seepoqpYmb20EsPFblHSdI0/QtPjsNMtY7a2j6Ko5J9Se596vUUVzN31ZulbQKKKKACiiigAooooAKKKKACkJABJIAAySTgAUteffEvWJnFp4WspWjkv1M15Ip5S3Bxge7Hj6VUI8zsJt9CLVviPd3lzLaeFbWKWONij6ldZ8rcOuxerVz8t14mvG3Xfiy+BPO22jWNR9BTooY4IY4IUEcUa7UQDgCn10pJbI6I4eP2tWRpd+JrbBtfFuoZHQXCJIp/Stiw+I2raUQviWwS5tOhv7FeUHq6dfyrLo6UNJ7ocsPB7aHrVneW2oWcV5Zzxz20y7o5YzlWFTjBPJwO5rxjStVm8Eaj9ut97aJM4+3Wi8iLPHnIO2O4r0HxRq7y2ttomjSCXVNZXZblDny4T96Y+gA6eprF0nzJLqcs24XUuhofDQG8tNZ13+DVNSlki/65qdi/wDoNdzVHRtLg0XRrTTbYYhtoljX3wOtXq9NKysec3d3CiiimIKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACqepapY6PZPe6jdRWtsn3pJXCgVh+M/Fp8N2lvDZW6XusXsohtLQvt3N1LN6KBya5QaB4h8Q6tY3njK6024tbMtJHp9pE3lmQjgsWPzYrOdSMNy4QlLYPGXiHR/E1z4VGk6jbXsX9rp5vlSAlMKSMjqPxrsWOWP1rgPHPgyGQweJtEs4otY0thP5cSBFuUXkqQP4sdDXYaNrFrr+j2urWbZgukDgd1PdT7g1xVp+0tJHVSjyNxZeooornNwoormn+IPhKPU5NPk121W4jOGznZn03gYz+NNRb2E5JbnS0VWtNRsb9d1ne29wD/wA8pA38qtFWHVSPqKQ7oSiiigAoorM1vxDpXhy0S51a8W2idtiZUszt6AAEmmk3ogbS3NOis3RvEOkeIbcz6TqEF0g+8EOGX6qeRWlQ01uCaewUUUUgCiiigAopGYKrOzBVUZZicAD3NcDrnxZ0XTp3tdKhl1i6U4byDtiU+7n+maqMXLYaTb5Yq7O/ryDXJDcfEjxBI55gWG3QHsoXP8zU0HxjvUk3X3hki37m2uN7gfQ9apa9qVhd6zF4s02fztH1RUt7p8YNrcKMAOP4dwranCUXqVKE6VSPtYuPqrEtFBGDiitDsCiiigBGjWVTG6hkcFWU9CDwRXZfBPRrK30C61DZJLqAuHszPM24rFGcIif3VAxx61xU9zFZ28lzM22KJS7H2Feq/C3Sp9L8B2P2pClxdl7uRT1XzDuAPuARW1Hqedj2rRXU7OiiitzzQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK53xh4ttfCOlLcyxSXN1O/lWlrEMtNJ2A9vU10Vef/ABQR7BdB8S7Gkt9Hvt1yoGdsUg2F/wDgJwaTbS0Gtyh4U8KizVdc1uIXHiS5LSzzyHcYd38CdlAHHFa3iLxDZeGdKa+vN7lmEcEEYy88h6KorUSRJY1ljcPG4DKynIYHoRXA6/qGnWfxVsJ/EFzHa2Nnpplsmn+40pbDH/eHavMV5yvI7naEbRLEdv8AEa+hGo/2npmmSMN0eltaiUAdg8h5z64qPwmJvDfi6+8M3SokV/H/AGlZrHnYrn/WxrntnkVXXx7d+IfGWk6ToayWumXDu5v7iHH2sIMssYPb361k+N/Eeg6z4307TG13+yU0kvJPqkJJdXPHlx4Bz7npWkYSk+VrczcopcyZ60AScAEn2rn9b8Z6Rok62Zke91J+I7CzXzJWPuB90e5qvY+Ab/VrSOWfx9qt/psw3L5DqokX/eFdBBpXhP4c6NPqAhgsoUGZbmT5pJD6FjyxPpWkcLr7zFLE6aI5a40bWdd0+XUfGd8ugaBGu99OtpcO6+ksn9BXl3iXXrbxGsWmaTp8eneF7R829sqBWuGH/LR+9WfGHjPUPHl8GmV7bRYmzb2ZPMno8n9BWJW11FcsT6TJ8kdRrEYpadF+r/yKbaVZFgywCNh0aNipH5GtC1v9Z0/H2HX9StwOi+aXH5Gq09xHbKjSE/O4RQBkkmpah67n0M8BgqsmnTjdeVn+BuWvj7xrZ8f2vbXajtcWi5/MYrWg+LXiOLH2rRNPuB6xTGMn881xtFS4Re6OWeQ4OWya9H/nc9Dh+M6AZvPC97GB1MNwsnH5Cr+grqnjnWNJ8XXcdpa6Pa+cbG1GXlfOULOeg6dq830XQZ/FniKDRYTst1AnvZc42xA/dHuelfQlpZ2un2cVnZQJBbQrsjiQYCisanLDSO58vj8PSo13SpSbS3vbftolsc/rPgXRtXuPtsSS6bqa/cvrFvLkB98cMPrWU2veJfB+F8S241fSVOP7Vso9ssY9ZY/6iu7rjPiX4qXw14ZaCF0Go6jmCBW6Kp4Zz7AVnBuTUXqcco9VuasXjfwrMqsniHTsMMjdOFP5GrK+J/D7j5dc04/S4WvnaDT7WK2jj8mJ9q8uVB3H1zTjYWZ62sR/4DW3sI9z6BcP4hxTc1f0Z9FjxBoh/wCY1pv/AIFJ/jWVrnj/AMOaFZmaTUYruU8R21o4kkkb0AHT6mvny9jsoHW3t7GGW7f7qBRhfc1Pp2lxWClzh7hvvSY6ew9BTVCO7ZlDJa063s1NWW7XTy835dOp0HiLxTrfi9yt/IbLTc/Jp8DcEf8ATRv4j+lZccaQxiONFRB0VRgU6itUklZH0+EwNDCRtTWvfq/n/SDNSabqcWgak891ALjRr5fI1S1IyHQ9HA/vA85qOkdFljaNxlGG1h7UBjsJHF0XSl8vJ/1+BvI1xp1w8dhaatqmgBQ8F99ikDRKf4WyPmA9RV+1vLa9jElrcRzL/snkfUdRXf8AwX8QvqfheXRbt913pT+Tk9XiPKH8uPwroda+HHhfXJmuLjTViuW/5b2zGJ/zXGat0k1ofnaxNSjJ06i20PJsH0NR3E8NpC01zKkMa8lnOBXe/wDCmrBX/deIdaWP+4ZgwH44rV0j4V+GNLuUupbaTULlDlZb6Qy7T6hTwPyqVRfVmjx8baI4Xwb4QuPGV/DqWo28kHh63cPFFKu1r1x0JHZB+te3gYGB0oACgBQABwAKWt1FJWR59SpKpLmkFFFFMgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACobq1gvrSa1uYllgmQpIjDIZSMEGpqKAPCdZj8T/D28g0uDVlttBR2ayvLi2M8YU/8spccrt7GqlnqfgnT5JfEHibWoPEGsu5YNFEzxRDskaHgV79JFHNGUlRXQ9VYZB/CvO/i54chufh1evp9nDHJaSJdlYogN4U8jj2OfwrKVFN6aGsZ6pMw5/iF4U1bwlPfPfNp0i77eJAgNzExH/LNe2R3rx22tLdb25ntbWSGylAEaXLB5W9WY9s+lWEhtpZFvFhjMjqCHxzipqyjFR2PusvyRUKqrTnzW2t+v8AkbHhLxrqPw/uS0Cvd6I5zPZbuYz/AHk9PcVV8Q+J9R8dagmpalIBZqc2dkjZSMf3m9WqjUFtaR2nmCIsEdt2w9FPfFacztY1WS4eGLVeMdO3RPvb9Oj1J6KKMgHJ6Dk1B7RnP/pWuonWO0Tcf98//WrRrO0cF7eW6b71xIz/AIZwK0abOTBe9TdV/bd/l0/CwUUUUjrLvh/X5PCXiaDWdjSWUifZ72NRk+WTkMPcHmvoC0u7a/tIruznSe2mXdHKhyGFfOX4Vd8PeINa8ITudHkjlspDuksLgnZn1U/wms6lPn1W58xm2VVJTdegr33X6r9UfQ4BP09a+fvHuqWnjDxm8sUayWGmx/ZUk/57Nklj9ASazvGfizxH4hiE99cm0t0cBLWzYhYwerMepNV7RIY7SJbchoQvyle9FOlye89zlyzLJTxH+0Rsoq9nu+112/4BU/st7Y7tPunh/wCmb/MhqK41DUraFhJYgydFljOVHvitalBI6Gtb9z6SWCSTVCTh6bfc9vlYp2FpHbw+YGMkso3PK3Vv8BVuiikdVKnGlBQirJBRRRQWFFFFAHZfCK4kg+Jhhj+5c6e/m/8AASMfzr6Frw/4H6d9p8Q63q7D5bdEtIz7nlv6V7hW8dj8yzacZ42q47XCiiiqPOCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqK5t47q1lt5lDRSoUdT3BGDUtFAHyZeaZLoWtaloc2d1jOyof70Z5U/lUdek/G3QTZ6pp/iiFP3MgFpeEDp/cY/yrzY8GsJqzP0XIsX9YwiT3jo/0/AKKKKk9kjmnit498rhVzgZ7/Sqd/fCLTpnMNxGXQiNpIWUMT6EivSfg7pGk6n4u1KfUljnvbOKM2cEuCFU53OAepBwM9s17pqGnWeq2Utnf20dxbSqVeORcgitIwVrnx2ZcQV6dadClFJK6139f8j5MtIvIsoIv7qAVNWj4l8Pv4R8V3mhM7PbqBNZyOclom7H3B4/Cs6oasz6bAYiniMPCpT2t91tLBRRRSOsKKKKADtjGQeCDWc9jNZyNPppAB5e3Y/K309DWjRTuY1qEKtr7rZrden9W7lW0v4rvKAGOdfvRPww/wAatVWu7GG8wzgpKv3JU4ZfxqsL25sCE1Bd8XRblBx/wIdqLX2MfbzoaV9v5lt8109dvTY0qKajpKgkjYOh6MpyKJJEiQvIwRB1LHApHXzK3NfQdVS71CO2YRIpmuW+7EnX8fQVXN5c6gSmnqUh6NcuOP8AgI71btLKGzU+WC0jffkblm/Gna25x+2nX0oaL+b/AOR7+u3qOtVuRETduhkY52oOEHp71MW2gsf4RmiobxtljcN6RmkdP8Km9b2XU97+C+nfYvh3bTuB5t7LJcMfXJ4/QV6FXP8AgaBbfwNokSDAFpH+ozXQV0n5NKTk231CiiigQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAGdrmjWniDRbvSr5N9vcxlG9R6Ee4618yar4c13wteSafqOm3syROVhu4IGdJk7HI7+1fVlJik1fc7cFj62Cm50nv32PlzTPCvirWyBp3h67VD/wAtbweQo/765rttJ+B2o3JWTXteWFD1t9PTn8Xb+gr27FFJRSN8RnONr6SnZdloebal8O9G8JeHbnVvDltLDq+nI11Fcb2eSXaMtG3qrDIx7132l38Oq6VaX9u6vFcRLIrKcggjNWz05ryG98Uw/DT4hDRY42fQL8LO0I5+xyOxBKD+4TyV7Z4qjzUnJ6amz8W/Bx17Ql1ixX/iZ6WrSIB/y1i6un5cj/69eFRSLNEkqfdcZFfWzsjQMTgoVJ+oxXyNbbQJ1T7i3EgT6bjis6i6n1HC+Jkqk6D2av8APRE1FFFZH2gUUUUAFFFFABQeQQQCD1B6GiigDPfSYhIXtZpbVjyREflP4UqaTEXD3M0t0R0Ep+UfhV+indnL9Rw978ny6fdt+ADAAAAAHQDtRRRSOoKhu08yyuEHUxn+VTUcd+nQ0EzipxcX1PpPwBdpfeAdDnQ5DWiD8QMH+VdJXlnwN1bz/C11o0rZn025ZQCeTG3zKfp1Fep10n5NUg6c3CW60Ciiua17x74d8O3Atby/El633bS2UyzH/gK9PxxQQdLRWR4c8Saf4p0s6hprSmJZGidZYyjo69VYHoeRWtQAtFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFYeteMfDvh441XWLW1cfwM+W/75GTQBuUlefXHxStNUxZ+ELK51jUZflibymSBP9p3PQCmx/DW61sfafGHiC/vrlufItZjBBF7KF5/GgD0NmCqSxAA6k1z2o+PPCmlSGO81+wjccFRKGI+u3OKw0+EPhxfka41aSA/ehe/kKn681v6d4I8MaSm2y0Oxj9W8kFj7knkmgBdJ8aeG9cuPs+m6zaTz9ow+GP0BwT+FeIfGLzbj4i3SRf6yKwhMfHRgxYfrivbdX8EeG9ch2Xuk2xYDCSxrskT/dZcEVxF/wDCXwkdSabV/EN7JKVVFW4vgrKo6DPUik1dHThK0KNZTmrrXT1TR2/hTxFZeJ/CVrqcTAI0W2eMnmJwMMp+lfMFnsKTGIkxfaJfLJ6ldxwa9sHwqm0K1ubnwNr89q9yhWS3uW863nBBHPoeeDXj1xoOv+G4ha6roV+nlZBmiiMkZ98jtUzTaPV4fxFGhipSqy5Vayv6oZRUUFzDdIWhlVwOoHUfUVLWJ99GcZrmi7phRRRQUFFFFABRRRQAUUUUAFFFFABRRRQBveBPEB8L+OrK7dsWV/i0uvQEn5G/OvoTXvEWleGdPa+1a8S3h6KDyzn0VepP0r5fs9Mv9e1yy0bSlRrx5BMTJ92NUOct7V7to3gp/wC1f7d8T3a6trP/ACzJXENsPSNO31qamJhRj72/Y/Pc+p01jpez8r+pSa68XePeYPO8NaA/R2H+mXC/T/lmD+ddFoHhLRPDUJXTbJFlbmS4f55ZD6s55NbdYfjLWRoHg/VNTBxJDbt5Y9XIwo/M15NTEVK8uX8DylFROR8BeM/DmgeGL641LVYIZLrVLmYQg7pDl8D5Rk84rXb4l3eofL4f8J6peKeBNcqLdP8Ax7kiqvgjwlp3h/w3po+wW41BrdXuJ2QFy7DJ5P1x+FdTk+tepLE8ukUXHD31bMTSvG2uR+JbDSfEmjW1kupB/sktvceZ86jJRvfFd9Xl/jom1fw3qqjmy1eLLeiyfK39K9PByM10UZucbsxqQ5ZWFooorUzCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACoLu9tbCEzXdxFBEOryuFH61x3i3xre2mqp4b8L2a3/iCVdzbv9Vaof45D/Sqth8LYLyVb7xhqNxr1+eWSRituh9FQdvrQBV1DxLq3j3UX0bwXdG20yI7b7WtmR/uRZ6n3rofD/w68OeH13pZi7vG5ku7z97I5+p6V0lnZWun2qWtnbxW8EYwscahVH4Cp6AI4reG3XbDFHGvoigD9KkoooAKKK5b4heIJvDng28u7MZv5dtvaKOvmyHap/DOfwoAwdZ8Q634t8R3PhjwlcCztrQ7dS1fG7yj/wA84vV/ftVm2+Dvg+OL/TbOXUbk/wCsubuZndz69a6Dwd4ag8J+GbTS4sNKq77iXqZZTy7E98n9MVvUAeZXXgfWPBTtqngW8mkhX5p9FupS8cwHXYTyreldb4T8W6d4w0o3NpujmjPl3NpKMSQP3VhXQVkQeGdLtvEk+v29v5V/cRCKZkbCyAHILL0J96AOR8e/CvTvEcEmoaTHHYa4g3JLGNqTH+64HBz614STcW109jqNtJZ38ZIeCVSpyO6+or67rlvHXgqx8aaFJbTIsd9GN1pdAfNE46c+nqKmUbnqZZmtXAy01i91/l5nzjRV7QvCvi7X0uUs9Mt99nO1tcSTThQJF68Vvn4U+NhGGA0lj/cErZ/PFcsqkIu0mfYPiDA/zP7mclRWtfeD/GOmZ+0+G7iZB1ezYSj8hzWFLdi3cpdW91bOOqzQMv8ASqUlLZnTSzbBVPhqL56fnYnoqqNRtmYKhlkY9FjiZif0rotE8F+KPEsyra6c+nWh+9eXq7cD/ZXqTRKSirydgr5tg6Mbuon5LV/gYM91BbbRK+Hb7qKMs30FbOleE/FmugPp+gyRQnpNet5Sn6A8mvaPCnw60Lwqglih+2agf9Ze3IDOT7Doo+ldb1rgqY5LSCPmcTxBiajtR9xfe/x0/A8IX4R+Mjy11pCe2WpG+EvjRfuz6Q/4sP6V7xRWP12qcH9qY7/n6zwRfhP41J+Z9IH/AG0b/CrMPwd8VyyAT6tpluh6tHGzEV7lRQ8bVB5pjWrOqzlfBngPTPBlvKbdnub+f/X3k3339h6D2rqqKK5ZzlN80tzhd27sK8++ITf21r3hzwpHytxcfbbvHaGLnB+p/lXoJIAJJwB1Nea+D5D4g8UeIPFr8wySf2fYn/pih+Zh9Wrowsfec+wkrtI7bvxxRRRXQdZzHxDhMvgLVHVdz26pcIPdGBrv9MuBd6VaXAIIlhR8j3UGuD8ba1peleHby11Cb99eQPDDbRjdLKxGOFHP41s/DC/Oo/DnRZWB3xwCFt3XK/Lz+Vd+Fvys4sRbmOuooorqMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiimTTR28Mk0zqkUal3djgKBySaACaaO3ieWaRY40G5nY4AHqTXDX/xa8OwzPb6Yt5rNypIEenwGQE+m7p+tYFra3/xg1F76+ea08GQSFba1QlWv2U4Luf7ue3/669P07SrDSLRbXTrOG1gUYCRIFH/16AOKs774mauDepp+j6TbNzHaXheSbHqxXgfSnzeIPiDBH9k/4RC2mvG4S5ivR5A/2mzyPpXesyopZmCgdycVz3ibxro/hexE93OJp5Dtt7SA75Zm7BVH86AOfTxzr3h91HjPQPstoSFOo2T+bCpP94dVHvUnivxxctc2WgeDhDf61qCCRZg26K2hP/LViP0rN1j4pXmk6JPPr3gbVLeJkxtfZJEwbgBj268jFZXhNbP4a/D2TxBNp6jWNal3W1lGMsS5zFCvfAByaAOp06HQvhfos97r2sJJqF7J5t3dy/6y4k9FUckDsBVSL4z6DJ+8fTNbjtM8XTWLeWR65HapvCngKSW6/wCEk8Y7NR16b5ljkG6KzB6IinjI9a9AKqVKkDaeCMUAUNG13TPEFgt7pN7Dd27cb42zg+h9DWhXnPi3wxL4Zml8Y+EYRb3sA339jEMRXsI+9lRwHA5BFdvourW2u6LZ6paNmC6iWRPbPagC/RRRQAVi6/4btvEMultcyuq2F4l2qL0crnAPt3/CtqigArD8Y+IB4W8JajrRj8w2sW5EP8TEgD9SK3KpavpNnruk3Ol6hF5tpcpskTOMj/EHB/CgDxP4P/FLxJ4n8XzaTq7pdQzRPMrLGFMJGOBj+HnvXvNcx4S8AeH/AAVHKNHtGWWbiSeVt7sOwz2H0rp6ACiiigDzj7G/g74ls6MX0vxOxyD/AMsbpFyPwZQfxFdxXI/EmQG68JW6f6+TXIGQey5Lf+O5rrj1rxMxglUTXVG1N6BTJIYpv9bFHJ/vqD/On0VwbF2Io7aCE5jgiQ/7KAVLnNFFFwsFFFFIYUUUUAFFFFABRRRQBx3xO8Rjw54LumjmWO7vP9Ft2LY2s3Bb8Bk1z2g+JbLT9Gs9C8K6Rf64bOJYjLbRbIC+MsxkbjkknIzWna6fbePviDd3N7Clzomg/wCjQI4yktyeXb0O0cfWvSoYYreFYoY0jiQYVEUKAPYCvcwuFSpLm66mXtHF6HlmoeIfFPh17PUPEek2Fpo1xcLBKYZy8lvu6Ox6YzwakPiTVfFFzJYeCbZJY1bZNrNypFvF6+WP+Wh+nFeg6/olp4i0K80m9XMFzGUJxkqezD3Bwa5v4canKdKn8OagkcWq6G4tZ0RQodMfu5AB2Zf610fV4XvYPbTta5a8M+AtM8PztqE8kmp6zKP3uo3fzSH2UdEX2Fc/8LdRg0ubXfCd3PHHd2GpSmJHbBkjc7gRnqK9Kd0ijZ5GCIoyzMcAD1rwhtAsfH/iXxlq1s6+Yk0cOmX0bEbJI1+8pHUZ4qqtWNGHNLYiMXJ2R7xmqsup2EEgjmvbeNycBWkAJNeWppXjnxFbRx+JdeTSrOJAr2+mHDy4HLPIemfY4rlIPBujeM9c/srwzp7HT7aUG/165kaRnIP3IyTgn3xWcMVCc+Snr+Q3TaV2fQ4ORkUtQ2lslnZwWsWfLhjWNcnJwBgZ/Kpq6SAooooAKKKKACiiigAooooAKKKKACiiigAooooAK4f4u3Ulr8M9W8tmUyhIWYdlZgG/DGa7iq97ZW2pWU1neQpNbToUkjcZDKeooAr6FaW9hoGn2lqqrbw20aRhemAoxWL4v8dad4VjS3Cte6vP8trp0HMkrHpn0Huax0+HGq2cH2HS/G2sWmmqf3cGVZol/uq5GcCtjwz4A0XwxcyXsKS3epSjEl9dv5krfQnoPpQBz1h4A1PxQf7U8d6jcSTyfNFplpM0cFqOw4PzN6n+ddHofw78K+HdQ+36fpUa3naeV2kZfpuJx+FdRVe+vIdOsLi8uHVIYI2kdmOAABmgDzcWknxK8c3DXjEeGtBn8tLbtd3A6s3qo9KtaWq+L/inealIN+m+HF+y2i/wm5YfO/4DA9jVr4SRzN4Ma/miaP8AtC8mukDdSjN8prr9N0mx0iOaOwtkgSaZp5Ag+87dTQBdqC7vbWwi827uYbeMnAaVwoz6c1PXzJ8f5ddu/HENm9vcHTo4U+yBFJWRiPmIx3zx68UAfTCtHNGGUq8bjgg5BFJDDFbxLFDGkUajCoihQPoBXG/CfSNV0T4eadZ6wXFyAziNzzEhOVU/QV21ABRRRQAUUUUAFFFFABRRRQAUUVWv7620ywnvryVYreBDJI7HAAFAHCakv9qfGqxt5m3Q6TpbXcaekkj7Mn8BXa1wnw/F1rl9q/jO8iMQ1Vljso2HK2yZ2n8Sc13deBjp89Z26G8FoFFFFcZYUUUUAFFFFABRRRQAUUUUAFc34416TQvD7C0G/Ur1xa2UfdpG4z+HWukyBkk4A6k1w3h1f+E08eXHiNxu0jR91rpwP3ZZf+WkvvjoK6sJR9rU12RE3ZHWeEfD8fhjw1aaYh3SIu+eQ9ZJG5Zj+NblFFfQmAV5j8R7ifwp4k0XxXpMInv52OnzWQODdoRlfxU969OrzfxurXvxL8HWXVIFuboj3CgCs6s+SDl2Q4q7SMmbw14k8YSCbxpqnk2JOV0bT2Kx/SR+rGun/wCJT4Z0UnFvp2m2y9htRf8AE/qayNZ8cWFhe/2XpkMms6y3C2Vp820+rt0Uf54p+lfD2/1++i1jx1cJcvGd9vpMJ/0eD/eH8Z/zzXjQo4jGPmqO0f62R1ucKWkdzKtrbV/ilJtj+0aV4QB+aQjZPfgdh/dSvVNL0uy0bTobDTraO3tYV2pGgwB/9erSIsaKiKFRRgKBgAU6vZpUoUo8sEcspOTuwooorQkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK4H4wSyL4Fa3DFIbq6hgnYdo2cA131cb8Vio+GOukqGP2f5c9jkYNAHV2VtFZ2MFtCoWKGNUQDsAMCp6z9BDr4e0wSMXcWkQZj1J2DJrQoAKa0aOQXRWK8gkZxTqKACiivCfi38YdU0LXJfD3h/bBLAoFxdOgY5IzhQemM9aAPbL7UrLTLcz313DbRDq8rhR+tef618c/Bek7kgu5dQlHQW0Z259Nxx/Wvm5YfF3ji7aQJqOrSnksdzAf0FW/B3w91XxZ4o/sVo5bExqzTyzRNiMDt9T0FAHp99+0o/mkWHh9fLz1nm5I/AV6r8PfGZ8c+GhqzWD2bCVoihO5WxjlT6c/pXLaB8A/CmlMkuoefqcy84mbamf90dfxr0+1tLeyto7a1hjhgjG1I41AVR7CgCaiiigAopks0UEZkmkSNB1Z2AA/E15/q/xHuL3WF0TwPYxa1frzcTs5Ftbj/acdT7CgDtNZ1mw0DSZ9T1K4WC1gXczN39h6k+leYR2fiH4qNFea0DpXhTeJIdPQ/vbtQcgyHsD6f/rqDV/BfxA1/U7HU9e/snUobUlk0lZWiiDdiT/EfrXSHxpe6TiPX/CmpWCKMedaqLiHj3XkD8K5MVKso2pL5lRt1LPinU73Tk0vQfDwhi1TUJBDblk3JbxKMvIV7hVHT1IqrZ6p4h8PeKbHQ/El3bajBqauLO9hh8plkQbijqOORnBHpU/ga3l13VL3xpexugus2+mxSLgxWyn7xB6FyM/TFM8TEXnxU8K2g5+zW9zeN7cBB/6Eaw+qwhh25rW1yuZuWh19FFFeMbBRRVTVNRh0nSbvUZyBFbRNKxPsKaV3ZCOMv9R8Wa547vtH8M6la2tjp8CfaZprcSATNn5B74waujQPiQvP/CW6U3t/ZuP61f8Ahrpctl4WF/eL/p2qyNe3BPX5+VH4Liuxr6GnhacYpOKuYOTOC/sP4ikf8jPpIPvp+f603+xviQh/5GLRnH/XiR/Wu/oqvq1H+VC5mcCdH+I5/wCY/ov/AIBN/jQNI+JDAqdf0VP9oWTH9M131FH1aj/Kg5meeT+CvGGrQNa6x4zH2STiRLGzELMvdc5PWu20jSrPQ9Kt9NsIRFa26BEUf196u0VrGEYK0VYVwoooqgCuR8XeALLxdfWd7Lf3tlcWyNF5lrJtLxt1U111FDV9wMbw94U0XwtZ/ZtIsY7cH78mMvIfVmPJNbNFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFZniHR4tf8AD99pUxwl1C0e70JHB/OtOigDjPh3r0l9pL6HqI8rWtHxbXUR6sBwsg9VIxXZ1y3iPwcurajb6zpl7Jpmt242pdxKGEif3JF/iX+VU7PxhfaVrdvofi61gtZ7kYtb+3Ym3uG/u88o3saAO1ooooAKwdV8FeGtcvlvdT0WzuroYAlkT5uOmfWt6igCC0srWxgEFpbxQRDokSBQPwFSiNFdnCqGbqwHJrkfGvjhfDTWum6faNqGu3522lmh/wDHmPZaw/8AhF/iU9qdRPjWFNSb5/sAtVNsP9jd1/GgD0yjNeLar8bNW8PIul6v4Z+z68pw/mzBLdh/fVvQ1y2s/FbxF40K6dY29xYQbcSx6ZmaaY+gcDCrVRi2TKaie+eIfEem+F9Il1PVZxFAnAHVnbsqjuTXmF/8bn1iVdE8KaLeNrV0fLiNygVYs/xEew5rjh4I8Y6qINQ8T6sdK021G2K41idWkiX/AGU6bvc16j8NbLwJYrLH4c1GDUdSYZuLmR907/n0H0oaSXmJOTe2gy0+EVnexRzeKdZ1TWbxhumWS5Kwlu4CDtXc6Toum6FZC00qygtIB/BEgUH6+tX6KkswdA8RjVr7U9NuoPsupafNslh3Z3IeUkU91I/Wt6vP/iAx8M6rpXje3Vtto4tdSVBzJauepHcq2CKlk+L/AIPUkQXlzdHt9ntJHB/HGKAO6ACjAAAryHVfF1tpfxe1S6nsNQvI7XTorJDZwGXYxYyNnHT+GtiX4xaSY2Fnouu3Vwf9XEti67z9TwKZ4HsNTtLHUNS1aL7PqWrXb3k0KtnygeFTPsAK58TKPs3F9TWlByYkPxZ0Oa5e2XTtbFxGAzxGxbcoPQkVYPxN0dP9ZpuuIPX+z3P9KzdH8R6b4f8AiX4iHiCdrKW/aEWc04IjljVegfpnPavUYporiJZYZEkjYZDIcg/jXPHAUZRT1FKTTscAvxR8OnrHqq/72nyD+lZet+Ibfx69j4a0WK9aK6uFe/me3ZFjgU5IJI7kAV6sVDdQD9RQFCjAAA9quGBpQkpK+hLm2IiLHGsaKFRQAoHQCnUUV2khRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABWfrOiaf4g0yXT9TtkuLeQcq3UHsQexHrWhRQBxEPhzxZoKhND8RRX1ovCWurxFig9BKvP5g1ILr4iOWP9m6FHtGQDdSHf7A7eK7OigDio/HGp2S+XrnhDVracfxWircxN9GU5/MVCmveOtYaSbSvDdpYWgH7s6tOUkk99ig7fxru6KAPKfhVatqeua/ruvnf4oiuTazRMOLWMdFT2PrXq1eY+JG/4Q34o6Z4iHyabrKixvj0CyD7jn+VemjmgDF8TeEtG8XaabLWLNZk/gkHEkZ9Vbt/KuGtdK+IXg1TpGi2+nazYONlrdz4hktx6SAffA9e9eqUUAcNpXw5gkuV1TxZdtr+q53Dzx/o8HtHH0/E81keKbGwu/id4YsdDtIotWs5DdXs9uoTyrbGNr467ieBXQeLPGUmnXUeg6BAuoeI7lf3cAPyW6/89JT/AAqPTqaueEPCkfhqzmknnN5q96/nX964+aaT0Hoo6AdqAOkooooAztf0qLXNAv8AS5wDHdQtGfxHH61518MtWkvvCn9nXZH2/R5mspwevyk7T+X8q9WrwjVLufwN8aNWuRbzS6TqNutxdLCuTGOhkCjrtI59iaxrw54abmlKfLLU9VLuRgscemaSqGna1pesRCTTdRtrpG6eVICfy61ZvbmHTrGe9vG8q3gQySO3AAFebZ7HfdHJeI7WLxT478P+GJI1mtbbOpXqMMrtGVRT9SelbFz8PbvR5Wu/BWsSaVLncbKfMtrJ7bTyo+lN+GGn3FzBqXi7UIyl5rc3mRI3WK2XiJfy5/EV6BXqUockEjz6kuaTZ5u/xQutEkXTPEnhy9h1tuIIbMedHd+8bdvoelUtQ1n4jTWNzrCDTdLitkMyaa6maSVRyQ7dFJHYVc1yT7d8ZtOiQ5XTdJllf2aRgB+grU1qUQaBqUrdFtZCf++TXn4zGTp1VTga0qSlHmZ0mgaquuaBYaoibBdQrLt9CRyK0a5X4bQvB8ONBjk+8LRSfx5rqq9Q5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDA8aeHI/FXhO+0p+JJE3Qv3SQcqR+Nc74P8YalffDlLtNNk1HWNOb7JdWiSBHZkOCQT3xz716DXnnw9jim8W+NL+yH/ABLpb9UjK/deRV/eEfjQBLb/ABd8PRMYtcg1DQrgdY7+1cA/RlBBqteePrzxXKNJ8BQvM8n+t1eeFlt7dO5XcBvb0FeiPFHKMSRo4/2lBojijhXbFGqL6KMCgDC8LeEdP8LWsggL3F9cHfd3053S3D9yx9PQdBXQUUUAFFFFABXnXjBRp/xQ8Halj5LppdPl/wBreuVB/WvRa83+KrvFf+DpE+8usx7fqRigDf1H4c+EdUlMtzoVoJTyXiXy2z65XFYt98HPD15aSWqXmr28En3okvWZD9Q2a9DoosFzhE8GeJ9OiWPSvHF55aAKkd5bRSgAdB0BrmtQ1b4i6d4rt9AvfEGmWy3cRe0vfsGRMw6oBnAYelewVwHxbuNJHhT7JeI8upzSD+y44D++FwPusvoB3PpUzu4uzsxrfUr+HPD13pd7qOp6tqf9parflRLcCIRKEUYVQo6CofiBdNb+C7yJP9bePHaRgdSztitzTFvU0mzXUXV74Qr9oZRwXxzWBqSHXfiRoGipzDp2dSuh2yOEB/HmvnaClXxKcnfX8junaFPQ9G0yzXT9LtbNAAsESxjHsMVaoor6Q4AooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK5Px9rd1pujRadpZB1nVpRZ2Y/uFvvSH2VcnP0oAzLjWfEXinxDe2Xhe8t7HTdPPlTX0sPm+dP3VB6DuaibxB8QPDr/wDE48P2+s2Y63GlvtkA9Sjf0rsPD2hWvhzQrXS7QHy4VwXPV2/iY+5OTWpQB5f4j+LekP4Vul0iWYa3Pi3gspomSVZG4zg+ma7DwT4fXwx4RsNM6zJHvnfu8jcsT+NaNzoel3l9Be3On20t1btuimaMFkPqDV+gAooooAKKKKACiiigArzr4kKLrxR4FsxyTq6zEf7Krk16LXnWoq2r/HDS7bOYtJ057ph/tOdooA9FooooA57xh4tsvCGkfbLlWmuJW8u1tY+XnkPRQP61yHh3w9qE2pP4m8TET67cL+7hHKWUZ6RoPX1NdF4t8FS+ItW03VrPVpNPvrBXWJhEJFw3U4PQ8dazB8Nr+8P/ABNvGOsXCH70UBWFT+QzXHiqVWquSDsuprTlGLuyTXPEuleHbZpdQuk80fctozullbsqqOcmpvh3ol7BBfeIdZhMWq6xJ5rRHrBEPuR/lya0NC+H3hvw/OLm008SXY/5ebhjLJ/301dPRhcHHD3d7thUquYUUUV2GQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcDoA/wCEn+Iera+43WekltNsc9PMH+ucfjxn2rvSMgjOK800qx8beCYrjR9L0a01mwaaSa1uTdCFk3sWIkB6kEnkdaAOr8U+LbXwzHaxmCa91C8k8u1srfBklPfA7AetY3/CyDZjdrPhbXtOj7ytbean5pmpfCXg++s9XuPEviW7S+1+5XYvlj91ax/3Iwf1P/189rigDmtK+IHhbWXEdprNt5p/5Zyt5bfk2K6NHWRdyMGU9wcisfVvCfh7W42XU9Is7gHqzRgN/wB9DBryPU4bXT9Ul0/4Z3uuT6lCwDw28vm2URz0YvwPwNAHu1FcZpviefw7oVv/AMJ/qel2epyMdqxSY3L9PX1xxW9p/iXQ9VQNYatZ3GeyTKT+VAGrRSAhhkEEe1LQAUUUUAFefeBydX8ceLNfBzD562ELeojHP61t+P8AxEPDPg6+vk5uXXybZO7ytwop3gLQT4b8F6dp8nNz5fm3DHq0jfM2ffnH4UAdJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABQelFFAHn3xNvru5k0TwrYXMltLrV15c8sZwywqMtg9s9K6i20/TfCHhuSLTbRILW0hZwiDlsDOSe5OOtcx8TtPvIV0jxTp8TTzaHcedLCoyXhIw+PcDmuu0zU9N8S6JHeWU0dzZXUeMqc8EcqfQ88igDivhv4fttX0oeMNajjv9W1bMu+ZQ4gjyQsaA9AAO1b+p/Dvwjq8nm3ehWvm9pIgY2H4qRWRYeBvEXhy3ez8OeLBDpyuXgs7yyWYRAnO0PkHbWdqaeKtI8TeG7nUfEguJ7u/FubG3hEcBi2ksccknHftQBpXPw0Nlbu/hrxFrGmXKjMSPdtLCW7BkbPFaHgTxXca/a3en6tElvruly+RfQqeCezr/ALLda66vNfG9ufCXi/TfHdqCsDlbHVkHRomwFkPupxz6AUAelUU2N1ljWRGDIwBUjoRTqAPNdYtpfFXxgs9JusDTNEtlvzGf+W0jHCk+wNelVwc7C1+NloRgfbNIdWPqUfIFd5QAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAIQCCCMg1xN38PEtNSk1Pwtqc2h3cp3SxRKHt5T/ALUZ4/EYrt6KAOME3xFt02G18P3hHAlEksRPuVwf51HoPhLVZvEx8T+Kr2K51CNDHZWtuCIbRT1IzyWPTNdvRQBheJ/F+jeEbD7Vq12se7iOFfmklPoq964lNL8S/FBfN1wyaJ4Xcgx6dGcT3Sg5Bkb+Eew/+vWVdLYaD8abq98aQebb3m06PfTEtDBgcoQeFPv/AI16fe+LfDumw+bd63YRIBnmdScfQHNAGrbwR21vHBCu2OJQir6ADAqSvPrj4taTPIYPD+najrtweFFpCRGT7ueBVdtJ8e+MRjV76Lw5pj9bSxbfcOvoz9vwoAj13VbLUPjJ4ZtNLmW4vrNJheeWcrFGV6MR3z2r02sHwx4O0TwjatDpVoEeTmWdzukkPqzHk1vUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAU9T0nT9Zsms9Ss4bq3brHKuRWDa/DbwZZTCaDw7ZCQchmQuR+ZNdVRQBDb2tvaRiO3gjhQdFjQKP0qaiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9k=" />
</body></html>
  • 效果

解释器模式(Iterpreter)

核心

  • 定义:对于一种语言,给出其文法表示形式,并定义一种解释器,解释定义的语句
  • 应用:dom 元素路径统计

示例一:统计元素路径

  • 需求:点击某个元素,计算其相对某个父元素的路径
  • js
// index.js
// 获取兄弟元素名称
function getSublingName(node) {if (node.previousSibling) {var name = "",count = 1,nodeName = node.nodeName, // 节点名称sibling = node.previousSibling; // 返回当前节点的前一个兄弟节点while (sibling) {// 元素节点,类型相同,名称存在if (sibling.nodeType === 1 &&sibling.nodeType === node.nodeType &&sibling.nodeName) {if (nodeName === sibling.nodeName) {// 名称相同,后缀数字+1name = String(++count);} else {// 名称不同,后缀加 '|' 和 名称count = 1;name = "|" + sibling.nodeName.toUpperCase();}}sibling = sibling.previousSibling;}return name;} else {return "";}
}// xPath解释器(冒泡遍历节点树)
var Iterpreter = (function () {// 递归函数return function fn(node, wrap = document) {var path = [];// 终止条件一(目标节点等于容器节点)if (node === wrap) {if (wrap.nodeType === 1) {path.push(wrap.nodeName.toUpperCase());}return path;} else {// 当前节点的父节点 不等于 容器节点(递归操作)if (node.parentNode !== wrap) {path = fn(node.parentNode, wrap); // 递归,返回 path 数组}// 终止条件二(目标节点父节点等于容器节点)else {if (wrap.nodeType === 1) {path.push(wrap.nodeName.toUpperCase());}}// 统计当前节点元素信息if (node.nodeType === 1) {// 获取兄弟元素的统计var sublingsNames = getSublingName(node);path.push(node.nodeName.toUpperCase() + sublingsNames);}return path;}};
})();

*html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><div></div><div></div><div><div><ul><li><span id="s1">1</span></li><li><span id="s2">2</span></li><li><span id="s3">3</span></li></ul></div><div><ul id="u2"><li><span id="s11">111<b>bbbbb</b></span></li><li><span id="s22">22</span></li><li id="l2"><span id="s33">33</span></li></ul></div></div><script src="./index.js"></script><script>const s33 = document.getElementById('s33')const l2 = document.getElementById('l2')const u2 = document.getElementById('u2')console.log(Iterpreter(s33).join('->')) //HTML->BODY|HEAD->DIV3->DIV2->UL->LI3->SPANconsole.log(Iterpreter(s33, l2).join('->')) //LI->SPANconsole.log(Iterpreter(l2).join('->')) // HTML->BODY|HEAD->DIV3->DIV2->UL->LI3u2.onclick = function (e) {console.log(Iterpreter(e.target, this).join('->')) // 打印点击元素相对u2的路径}</script>
</body></html>

行为型设计模式(二)相关推荐

  1. 中介者模式 调停者 Mediator 行为型 设计模式(二十一)

    中介者模式(Mediator) 调度.调停 意图 用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散 而且可以独立地改变它们之间的交互. 中介者模式又 ...

  2. 模板方法模式 Template method 行为型 设计模式(二十六)

    模板方法模式 Template method 上图为网上百度的一份简历模板截图 相信大家都有求职的经历,那么必然需要简历,写简历的时候,很可能你会网上检索一份简历模板,使用此模板的格式,然后替换为你的 ...

  3. 观察者模式 Observer 发布订阅模式 源 监听 行为型 设计模式(二十三)

    观察者模式 Observer 意图 定义对象一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖他的对象都得到通知并自动更新. 别名:依赖(Dependents),发布订阅(Publish-Su ...

  4. Java设计模式(二)创建型设计模式

    文章目录 三 创建型设计模式 3.1 单例设计模式 3.1.1 饿汉式(线程安全) 3.1.2 懒汉式(线程不安全) 3.1.3 优缺点 3.1.4 补充 3.1.5 框架中的使用 3.1.4.1 S ...

  5. 二、java设计模式之工厂方法+抽象工厂模式(创建型设计模式)

    创建型设计模式-工厂模式和应用 工厂模式介绍: 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象 例子: 需要购买一辆车,不用管 ...

  6. 备忘录模式 Memento 快照模式 标记Token模式 行为型 设计模式(二十二)

    备忘录模式 Memento 沿着脚印,走过你来时的路,回到原点. 苦海翻起爱恨 在世间难逃避命运 相亲竟不可接近 或我应该相信是缘份 一首<一生所爱>触动了多少人的心弦,一段五百年都没有结 ...

  7. 设计模式二:创建型-工厂模式

    创建型模式:工厂模式 文章目录 创建型模式:工厂模式 工厂模式 1.工厂模式:介绍 2.工厂模式:模拟场景 3.工厂模式:代码实现 4.工厂模式:总结 工厂模式 1.工厂模式:介绍 工厂模式 简单工厂 ...

  8. 《深入设计模式》笔记 -创建型模式二、工厂方法模式

    抽象工厂模式 亦称: Abstract Factory 意图 抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类. 问题 假设你正在开发一款家具商店模拟器. 你的代码中 ...

  9. 技术图文:02 创建型设计模式(下)

    创建型设计模式(下) 知识结构: 图1 知识结构 单例模式 – 确保对象的唯一性 Sunny 软件公司承接了一个服务器负载均衡软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量 ...

  10. 技术图文:02 创建型设计模式(上)

    创建型设计模式(上) 知识结构: 图1 知识结构 简单工厂模式 Sunny 软件公司欲基于 C# 语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,如: 柱状图(histogram) ...

最新文章

  1. struct 类型指针技巧
  2. md3600i存储服务器连接 iscsi+multipath配置
  3. 前端学习(3157):react-hello-react之一个简单的helloworld
  4. 温昱:架构实践全景图
  5. 【debug】mount: unknown filesystem type ‘nfs’
  6. Functional Interface JDK1.8
  7. xUtils项目框架
  8. 【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程
  9. Qt5.4生成安装包过程
  10. 综合评价模型的缺点_【必备】目标检测中的评价指标有哪些?
  11. python读写将excel转换为xml_Python实现将Excel转换成xml的方法示例
  12. 被圈粉的微信小程序纯UI组件colorUi
  13. python英文词频统计代码_python词频统计_英文
  14. oracle job定时报错,Oracle定时任务Job笔记
  15. [转]如何在NIOS II中读写EPCS剩余空间
  16. 京东咚咚架构演讲读后感
  17. 上门洗车APP --- Android客户端开发 之 网络框架封装介绍(二)
  18. 190502 Expressing Belief
  19. NCCL+Ubuntu20.04安装
  20. iPhone4/4s 5.1.1版本越狱后无法连接iTunes,出现0xE8000012错误的解决方法

热门文章

  1. 数字音频信号--Dither
  2. Kinect2.0在win10平台上时断时续问题的解决办法
  3. 一元高次方程c语言实现,c语言实现一元二次方程求解
  4. sonar代码审查问题分析
  5. 统一身份管理项目最佳实践
  6. 留美学子安全手册,这个可以有
  7. 华为云SSL证书申请流程
  8. 加一 — Python
  9. 【大数据实战】苏宁大数据离线任务开发调度平台实践:设计与开发过程中的要点
  10. python笔记三之面向对象(继承,封装,多态)