设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。
GoF提出的23种软件设计模式,这里介绍几种常用的设计模式。
单例模式
单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
示例代码如下:
<script type="text/javascript">
//单例模式
Math.random()
Math.random()
console.log(Math==Math)
class Dog{
//统一接口
static shareInstance(){
if(!Dog.ins){
Dog.ins = new Dog()
}
return Dog.ins;
}
constructor(){
}
wang(){
}
}
let d1 = new Dog()
let d2 = new Dog()
console.log(d1==d2)
let d3 = Dog.shareInstance()
let d4 = Dog.shareInstance()
let d5 = Dog.shareInstance()
console.log(d3 === d4)
console.log(d5 === d4)
</script>
单例模式的适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次,就像微信PC版。
组合模式
组合模式的特点:
1.组合模式在对象间形成树形结构;
2.组合模式中基本对象和组合对象被一致对待;
3.无须关心对象有多少层, 调用时只需在根部进行调用。
想象我们现在手上有个万能遥控器, 当我们回家, 按一下开关, 下列事情将被执行:
1.煮咖啡
2.打开电视、打开音响
3.打开空调、打开电脑
我们把任务划分为 3 类, 效果图如下:
示例代码如下:
<script type="text/javascript">
const MacroCommand = function(){
return {
lists:[],
add:function(task){
this.lists.push(task)
},
excute:function(){//1.组合对象调用这里的excute,
for(let i = 0;i < this.lists.length;i++){
this.lists[i].excute()
}
},
}
}
const command1 = MacroCommand() //命令1
command1.add({
excute:()=>console.log('煮咖啡')
})
const command2 = MacroCommand() //命令2
command2.add({
excute:()=>console.log('打开电视')
})
command2.add({
excute:()=>console.log('打开音响')
})
const command3 = MacroCommand() //命令3
command3.add({
excute:()=>console.log('打开空调')
})
command3.add({
excute:()=>console.log('打开电脑')
})
const macroCommand = MacroCommand() //组合对象
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.add(command3)
macroCommand.excute()
</script>
可以看出在组合模式中基本对象和组合对象被一致对待, 所以要保证基本对象(叶对象)和组合对象具有一致方法。
观察者模式
核心思想:观察者只要订阅了被观察者的事件,那么当被观察者的状态改变时,被观察者会主动去通知观察者,而无需关心观察者得到事件后要去做什么,实际程序中可能是执行订阅者的回调函数。
Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态,或者说执行对应对象的方法。
这种设计模式可以大大降低程序模块之间的耦合度,便于更加灵活的扩展和维护。
场景、当观察的数据对象发生变化时, 自动调用相应函数。比如 vue 的双向绑定。
示例代码如下:
<script type="text/javascript">
var obj = {
data:{
list:[]
},
}
//defineProperty可以观察obj对象的list属性的使用
Object.defineProperty(obj,'list',{
get(){
console.log('获取了list属性')
console.log(this.data['list'])
return this.data['list']
},
set(val){
console.log('值被更改了')
this.data['list'] = val
}
})
//获取了list属性,那么get方法就会被调用
console.log(obj.list)
//设置了list属性set方法就会被调用
obj.list = ['a','b']
console.log(obj.list)
</script>
工厂模式
工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。js中构造函数就是工厂。
示例代码如下:
<script type="text/javascript">
//文本工厂
class Text{
constructor(text) {
this.text = text;
}
insert(where){
const txt = document.createTextNode(this.text);
where.appendChild(txt);
}
}
//链接工厂
class Link{
constructor(url) {
this.url = url;
}
insert(where){
const link = document.createElement('a');
link.href = this.url;
link.appendChild(document.createTextNode(this.url));
where.appendChild(link);
}
}
let t = new Text('sd')
t.insert(div)
console.log(t)
// new Link('www.baidu.com')
</script>
抽象工厂模式
抽象工厂模式:流程==》 先设计一个抽象类,这个类不能被实例化,只能用来派生子类,最后通过对子类的扩展实现工厂方法。
示例代码如下:
<script type="text/javascript">
//DOM工厂
class DomFactory{
constructor() {
}
//各流水线
insert(){
}
}
//链接工厂
class Link2 extends DomFactory{
constructor(url){
this.url = url;
}
insert(where){
const link = document.createElement('a');
link.href = this.url;
link.appendChild(document.createTextNode(this.url));
where.appendChild(link);
}
}
</script>
策略模式
策略模式的定义:定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换。
策略模式的目的就是将算法的使用算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类(可变),策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context(不变),Context接受客户的请求,随后将请求委托给某一个策略类。要做到这一点,说明Context中要维持对某个策略对象的引用。
示例代码如下:
<script type="text/javascript">
//策略类
var levelOBJ = {
"A":function(money){
return money * 4;
},
"B":function(money){
return money * 3;
},
"C":function(money){
return money * 2;
}
};
//环境类
var calculateBouns = function(level,money){
return levelOBJ[level](money);
};
console.log(calculateBouns('A',10000))
console.log(calculateBouns('B',10000))
console.log(calculateBouns('C',10000))
</script>
代理模式
js中的事件委托就是典型的代理模式。需要将自己的事情由中介去完成的就是代理模式。
示例代码如下:
<script type="text/javascript">
const MyImage = function(parent){
const imgNode = document.createElement('img')
parent.appendChild(imgNode)
//提供一个方法,让外部也能修改图片的src属性
this.setSrc = function(src){
imgNode.src = src;
}
}
//直接添加图片,如果图片比较大,可能加载的比较慢,导致网页上的img一开始显示不出来
let myImage = new MyImage(document.body)
//写个代理 提供预加载功能
const ProxyImage = function(myImage){
//创建image对象
const img = new Image()
img.on
//监听img对象的onload方法
img.onload = function(){//http图片加载完毕后才会执行
//模拟网络很慢的情况
setTimeout(()=>{
myImage.setSrc(this.src)
},2000)
}
this.setSrc = function(src){
//加载时候先不加载大图,先加载一张小图(浏览器已经下载过的)
myImage.setSrc('loading.gif')//本地loading图片
img.src = src
}
}
//
let proxyImage = new ProxyImage(myImage)
proxyImage.setSrc('https://www.baidu.com/img/bd_logo1.png?where=super')
</script>
适配器模式
适配器模式主要就是解决两个接口之间不匹配的问题,比如苹果电脑上没有网线插口,需要讲雷电借口或者USB借口转化为网线插口。
示例代码如下:
<script type="text/javascript">
// 老接口
const zhejiangCityOld = function () {
return [{
name: 'hangzhou',
id: 11,
},
{
name: 'jinhua',
id: 12
}
]
}
console.log(zhejiangCityOld())
// 新接口希望是下面形式
// {
// hangzhou: 11,
// jinhua:12
// }
// 这时候就可采用适配者模式
const adaptor = function (oldCity) {
console.log(oldCity)
const obj = {}
for (let city of oldCity) {
obj[city.name] = city.id
}
return obj
}
let oldData = zhejiangCityOld();
//把老数据放在适配器中产生新数据
console.log(adaptor(oldData))
</script>