02适配器模式
设计模式第2天
复习:
原型: 是每一个函数天生拥有的属性 值是一个对象 constructor指向构造函数本身
作用: 实例共享数据
继承: 类与类之间的继承
1 构造函数式继承
2 类式继承
3 组合式继承
4 寄生式继承
5 寄生组合式继承
设计模式: 编目分明 经验的总结
结构性 行为型 创建型
简单工厂: 创建对象的方法
增强型工厂: 在一个工厂方法种 实例化另一个工厂 并对其增强 返回实例化对象
安全工厂: 称为安全类
工厂方法: 一个管理多个类的实例化创建的工厂方法
单例模式: 单个实例 只允许实例化一次 定义的时候已经被加载
惰性单例: 第一次调用的时候
静态变量: 一旦定义就无法改变的变量
适配器模式: 将一个类的接口 转接到另一个类的接口中
一、设计模式
1.1 适配器模式
将一个类(对象)的接口(属性或者方法)适配到另一个类(对象)的接口(属性或者方法),来解决类(对象)之间不兼容的问题,实现类(对象)之间的解耦
特点
结构型的设计模式
是用来解决接口之间不兼容的问题的
是对数据进行的一个拆分再封装的一个过程
适配器模式往往需要一些额外的开销,但是这些开销要远比修改原有业务逻辑的成本低
1.2 适配器模式应用
请求接口数据的适配
参数的适配
代码库的适配
创建一个tools代码库,实现了交互
领导要求换jquery,
将jquery的一些接口方法适配到原有的库的接口方法
<!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>
<button id="btn">切换显隐</button>
<h1 id="box">hello world</h1>
<!-- <script src="./js/tools.js"></script> -->
<!-- 引入jquery -->
<script src="./js/jquery.js"></script>
<script>
// 获取元素
// var btn = document.getElementById('btn');
// var box = document.getElementById('box');
// 获取元素
var btn = $('#btn');
var box = $('#box');
// 适配tools对象
var tools = {
bindEvent: function(dom, type, fn) {
// 使用jquery绑定
$(dom).on(type, fn);
},
css: function(dom, key, value) {
$(dom).css(key, value);
},
getStyle: function(dom, key) {
return $(dom).css(key);
}
}
// 绑定事件
tools.bindEvent(btn, 'click', function() {
// 设置元素的显隐
tools.css(box, 'display', tools.getStyle(box, 'display') === 'none' ? 'block' : 'none' );
})
// 如果当前项目代码量很大 并且在有时间的情况下 建议重构代码
</script>
</body>
</html>
1.4 观察者模式
定义:观察者模式,又叫发布订阅者模式,又叫消息系统,又叫消息机制,又叫自定义事件,解决主体与观察者之间的耦合问题
观察者模式是一个行为型设计模式
特点
1 解决的是耦合问题(类与类之间,对象之间,类与对象之间,模块之间)
2 对于任何一个观察者来说,其它观察者的改变不会影响自身
3 对于任何一个对象来说,既可以是观察者,也可以是被观察者
实现观察者模式
观者者对象必须具备两个方法
on 用来注册消息
第一个参数表示消息的名称。
第二个参数表示回调函数
trigger 用来触发消息
第一个参数表示消息的名称。
从第二个参数开始表示传递数据
通过闭包将接口返回,那么on和trigger对用户来说就是可访问的,闭包里面存储消息队列,对用户来说就是不可见的,因此是安全。
<script>
// 定义观察者对象
var Observer = (function() {
// 定义存储数据的对象
var _data = {
// 订阅消息的执行结果,就是存储数据,当fn执行的时候是在订阅消息中
// sendMsg: function() { console.log('sendMsg', arguments); }
// 第二次提交相同消息名称,执行同名函数的时候,后面打印的信息就会被覆盖掉
// sendMsg: function() { console.log('sendMsg222', arguments); }
// 因此为了避免出现上面的情况,则需要做一个安全校验,当检测到已经存在同名数据的时候,就给它推进去
// sendMsg: [ function() { console.log('sendMsg', arguments)}, function() { console.log('sendMsg222', arguments)]
};
// 返回接口对象
return {
/**
* on方法 用于订阅消息
* @type 消息类型
* @fn 回调函数
**/
on: function(type, fn) {
// 存储数据
// _data[type] = fn;
// 希望trigger 提交一个消息的时候,on中的同消息名称的两个都触发,于是做如下的改变
// 由于这里的改变,data[type]中的fn变成了数组,无法使用apply方法,因此trigger那里需要做一个安全校验
// 判断type是否已经存在
if (_data[type]) {
// 已经存在就直接放入
_data[type].push(fn);
} else {
// 变成数组的形式
_data[type] = [fn];
}
},
/**
* trigger方法 用于发布(提交)消息
* @type 消息的类型
**/
trigger: function(type) {
// // 触发消息
// _data[type] 返回 on 中的 fn函数,加括号执行,并将所需要传递的参数传给fn
// _data[type] && _data[type](100, true, 'abc', 500)
// 获取剩余参数
var args = Array.prototype.slice.call(arguments, 1);
// // 实现将数组中的每一项作为参数传递到一个方法中 可以使用apply方法
// 将数组中的数据,一个一个传递给 on 中的 fn函数
// _data[type] && _data[type].apply(null, args);
// 由于当提交消息的时候,相同的消息名称,下面的会覆盖上面的,因此,当发布消息的时候,只会返回下面的那一条
// 安全校验
if (_data[type]) {
// 遍历数据
for (var i = 0; i < _data[type].length; i++) {
// 逐条执行
_data[type][i].apply(null, args);
}
}
}
})();// 立即执行函数
// 提取并声明函数
function sendMsg() {
console.log('sendMsg', arguments);
}
function sendMsg111() {
console.log('sendMsg222', arguments);
}
function sendMsg333() {
console.log('sendMsg333', arguments);
}
function sendMsg444() {
console.log('sendMsg444', arguments);
}
// 返回接口
// console.log(Observer); 返回接口对象,因此可以通过打点方式调用接口
// 订阅消息
// Observer.on('sendMsg', sendMsg);
// Observer.on('sendMsg', sendMsg111);
// Observer.on('sendMsg333', sendMsg333);
// 提交消息
// Observer.trigger('sendMsg', 100, true, 'abc', 500);
// 多次触发:
// Observer.trigger('sendMsg333', 100);
// Observer.trigger('sendMsg333', 100);
// Observer.trigger('sendMsg333', 100);
// 注意: 在观察者对象中 只有先订阅才能去提交消息
</script>
- 在使用 trigger 的时候使用了 var args = Array.prototype.slice.call(arguments, 1);
- 在这里,目的是要使用传递进来的参数,arguments存储了传递进来的参数,但是又想要截取除了第一个数据,获取从第一个以后的数据,又想要将arguments转为真正的数组,想要使用 slice 这个方法,且slice()可以实现将数组转为真正的数组,Array原型中的原型存在call这个方法,且 [ ] 还可以 [ ] . slice . call ( arguments,1),因为[]是Array的实例,当在arguments实例上找不到slice方法时,会沿着原型链查找该方法
- [ ].slice.call(arguments); 这个意思就是把arguments里使用slice方法转为真正的数组,call第二个参数是0,传递slice 就是从0开始截取,将参数传递给slice
off 用来移除消息的方法,
参数同register
once 单次订阅方法,
参数同register
// 定义观察者对象
var Observer = (function() {
// 定义存储数据的对象
var _data = {
// sendMsg: function() { console.log('sendMsg222', arguments); }
// sendMsg: [ function() { console.log('sendMsg', arguments)}, function() { console.log('sendMsg222', arguments)]
};
// 返回接口对象
return {
/**
* off方法 用于注销事件
* @type 事件类型
* @fn 事件回调函数
**/
off: function(type, fn) {
// 该方法可以实现多个功能:
// 1 当我们传递type参数的时候 实现将对应的事件回调清空
// 2 当我们传递fn参数的时候 实现将对应的fn事件回调移除
// 3 当没有传递参数的时候 实现清空所有的数据
// 1
// if (arguments.length === 1) { console.log(123); }
// 判断是否传递了一个参数
if (type && !fn) {
// 清空对应的数组成员
_data[type] = [];
} else if (type && fn) {
// 2
// 从后向前遍历数组
for (var i = _data[type].length - 1; i >= 0; i--) {
// 判断传递的fn 和 数组中的fn是否匹配上
if (_data[type][i] === fn) {
// 移除该成员(函数)
_data[type].splice(i, 1);
}
}
} else {
// 3
_data = {};
}
// 需要注意的是:不要下面这种写法,因为当满足第二个条件的时候,会被第一个条件拦住
// if (type) {
// console.log(1);
// } else if (type && fn) {
// console.log(2);
// }
},
/**
* once方法 单次绑定消息
* @type 消息类型
* @fn 消息函数
**/
// 通过once绑定的消息 只能触发一次
once: function(type, fn) {
// 内部封装一个函数
function callback() {
// 一旦执行 就应该注销该事件
Observer.off(type, callback);
// fn要在内部函数里面执行
fn();
}
// 本质上也是通过on方法产生绑定
Observer.on(type, callback);
}
}
})();
// 提取并声明函数
// 这里为了添加移除事件,必须使用有名函数
function sendMsg() {
console.log('sendMsg', arguments);
}
function sendMsg111() {
console.log('sendMsg222', arguments);
}
function sendMsg333() {
console.log('sendMsg333', arguments);
}
function sendMsg444() {
console.log('sendMsg444', arguments);
}
// 订阅消息
// Observer.on('sendMsg', sendMsg);
// Observer.on('sendMsg', sendMsg111);
// Observer.on('sendMsg333', sendMsg333);
// 通过once绑定的消息 只能触发一次
// 通过trigger触发的事件,可以多次触发
Observer.once('sendMsg444', sendMsg444);
// 注销消息
// 传递一个参数
// Observer.off('sendMsg');
// 传递两个参数
// Observer.off('sendMsg', sendMsg);
// 没有传递参数
// Observer.off();
// 提交消息
// Observer.trigger('sendMsg', 100, true, 'abc', 500);
// 多次触发:
// Observer.trigger('sendMsg333', 100);
// Observer.trigger('sendMsg333', 100);
// Observer.trigger('sendMsg333', 100);
// 当尝试触发多次 once绑定的消息的时候 只能触发一次
Observer.trigger('sendMsg444', 100);
Observer.trigger('sendMsg444', 100);
Observer.trigger('sendMsg444', 100);
Observer.trigger('sendMsg444', 100);
// 注意: 在观察者对象中 只有先订阅才能去提交消息
// 先订阅,再移除,再发布
1.5 组合模式
又叫部分-整体模式,将对象组装成一个树形结构来表达这个整体,不论是部分还是整体,在表现上具有一致性。是结构型设计模式。其特点
是一个拆分合并过程。为我们提供清晰的组成结构,
通过对基对象的属性方法的继承,使成员对象间的基本表现,行为统一
成员对象的结构简单而又单一,这给我们带来了更多的组合方式。
组合模式实现步骤
对整体拆分 -> 得到不同层级的个体。 对个体组装 -> 组合得到不同的整体
所有个体都会继承同一个基类
通常基类是只能被继承,不会去实例化的的 (包含的是所有个体共有的属性方法)
新闻模块
整个新闻模块是一个根节点
每一行是一个枝干节点
每一行有多条新闻,每一条新闻就是一个叶子节点(因此不能包含子节点)
总结
是一个结构型设计模式
本质就是一个拆分合并的过程
整体与个体之间具有行为的一致性
对个体的不同的组装,可以是整体差异化
组合模式使整体结构很清晰
<!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>
<script>
// 组合模式: 首先要定义一个基类 包含子类所有的属性和方法 但是基类不要实例化
// 封装寄生式继承
function jisheng(father, child) {
// 定义寄生函数
var F = function() {};
// 让F的原型指向father的原型
F.prototype = father.prototype;
// 让child的原型指向F的实例
child.prototype = new F();
// 补回自身的constructor
child.prototype.constructor = child;
}
// 定义一个基类
function Base() {
// 定义属性
this.element = null;
}
// 方法
Base.prototype.add = function(child) {
// 追加实例中的元素(element)
this.element.appendChild(child.element);
// 返回this 为了链式调用
return this;
}
// 初始化方法
Base.prototype.init = function() {
throw new Error('基类不要去实现');
}
// 继承
jisheng(Base, Container);
// 定义容器类
function Container(id, parent) {
// 使用构造函数式继承属性
Base.call(this);
// 接收
this.id = id;
this.parent = parent;
// 执行初始化方法
this.init();
}
Container.prototype.init = function() {
// 创建元素
this.element = document.createElement('ul');
// 设置元素的id
this.element.id = this.id;
}
// show方法
Container.prototype.show = function() {
//上树
this.parent.appendChild(this.element);
}
// 继承
jisheng(Base, Item);
// 创建每一行(li)
function Item(className) {
// 使用构造函数式继承属性
Base.call(this);
this.className = className;
// 执行初始化方法
this.init();
}
// 初始化
Item.prototype.init = function() {
// 创建元素
this.element = document.createElement('li');
// 设置元素的class
this.element.className = this.className || 'li_item';
}
// 继承
jisheng(Base, TitleNews);
// 创建不包含的分类节点
function TitleNews(text, href) {
// 使用构造函数式继承属性
Base.call(this);
// 接收数据
this.text = text;
this.href = href;
// 执行初始化方法
this.init();
}
// 初始化
TitleNews.prototype.init = function() {
// 创建元素
this.element = document.createElement('a');
// 设置内部文本
this.element.innerHTML = this.text;
this.element.href = this.href;
}
// 继承
jisheng(Base, TypeNews);
// 创建包含的分类节点
function TypeNews(text, type, href) {
// 使用构造函数式继承属性
Base.call(this);
// 接收数据
this.text = text;
this.href = href;
this.type = type;
// 执行初始化方法
this.init();
}
// 初始化
TypeNews.prototype.init = function() {
// 创建元素
this.element = document.createElement('a');
this.element.href = this.href;
// 创建span
var span = document.createElement('span');
// 设置内部文本
span.innerHTML = this.type + ' | ';
// 追加元素
this.element.appendChild(span);
// 创建节点类型
var val = document.createTextNode(this.text);
// 追加文本内容
this.element.appendChild(val);
}
// 实例化对象
new Container('box', document.body)
.add(
new Item()
.add(
new TitleNews('消息称三星Galaxy A73手机将采用中国厂商的OLED面板', '#')
)
)
.add(
new Item()
.add(
new TitleNews('远峰蓝iPhone 13/mini今日正式发售', '#')
)
)
.add(
new Item()
.add(
new TypeNews('尼尔手游愚人节预告 英雄联盟手游国服消息首曝', '手游', '#')
)
)
.show()
</script>
</body>
</html>