常用设计模式

71 阅读5分钟

定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

单例模式

定义:在一个软件运行环境中,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现:创建一个函数,返回一个对象实例。函数中先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,在不同的作用域中使用同一个方法进行获取对象实例。

适用场景:

  • vue中的Store;

  • 一个单一的Class对象;

  • axios在不使用create的情况下就是一个单利;

实现

class CreateUser {
    constructor(name) {
        this.name = name;
        this.getName();
    }
    getName() {
         return this.name;
    }
}
// 方式一
var instance = null;
function init(name) {
    var instance = null;
    if(!instance) {
        instance = new CreateUser(name);
    }
    return instance;
}

const A = init('zhangsan')

// 方式二: 代理
var ProxyMode = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new CreateUser(name);
        }
        return instance;
    }
})();
// 测试
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
// 因为单体模式是只实例化一次,所以下面的实例是相等的
console.log(a === b);    //true

发布订阅(观察者)模式

观察者模式中通常有两个模型,一个观察者(observer)和一个被观察者(Observed)。从字面意思上理解,即被观察者发生某些行为或者变化时,会通知观察者,观察者根据此行为或者变化做出处理。

场景:

  • vue2.0版本的 数据变化后通知试图更新

  • vue 的eventbus传值方式

  • 商品倒计时场景,使用一个定时器来通知所有的商品倒计时

  • 许多,对象解耦操作和业务逻辑事件回调操作

export default  class EventBus{
	constructor() {
		this.subs = {}
	},
  // 存储-创建的订阅者
	on(event, cb) {
		(this.subs[event] || (this.subs[event] = [])).push(cb)
	},
  // 通知-观察者
	trigger(event, ...args) {
		this.subs[event] && this.subs[event].forEach(cb => {
			cb(...args)
		})
	},
  // 只订阅一次
	once(event, onceCb) {
		const cb = (...args) => {
			onceCb(...args)
			this.off(event, onceCb)
		}
		this.on(event, cb)
	},
  // 移除某个观察者
	off(event, offCb) {
		if (this.subs[event]) {
			let index = this.subs[event].findIndex(cb => cb === offCb)
			this.subs[event].splice(index, 1)
			if (!this.subs[event].length) delete this.subs[event]
		}
	}
}

使用

// 触发通知
this.$eventEmitter.trigger("onectGetUserinfo",{a:1})
// 接收器
var self = this;
this.$eventEmitter.on("onectGetUserinfo", (e) => {
    // do someing
    console.log(e)
});

工厂模式

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。

适用场景

  • 动态表单核心组件component
 <component
      :is="item.type" // 要渲染的组件类型
      :type="item.prefixId" // 组件标识
      :cusAttrs="item.componentsProps" 
      :cusListeners="item.function"
      @modelWatch="modelWatch"
  />
  • 需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性
    class Product {
        constructor(name) {
            this.name = name
        }
        init() {
            console.log('init')
        }
        fun() {
            console.log('fun')
        }
    }
    // 工厂 [ˈfæktri] 
    class Factory {
        create(name) {
            return new Product(name)
        }
    }

    // use
    let factory = new Factory()
    let p = factory.create('p1')
    p.init()
    p.fun()

装饰器模式(Decorator)

定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能,目前有一个提案将其引入了 ECMAScript。

装饰者模式适用的场景:

  • 原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;

  • 函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。

  • 动态地给某个对象添加一些额外的职责,是一种实现继承的替代方案

函数的装饰

function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

用AOP装饰函数实现装饰者模式

Function.prototype.before = function(beforefn) {
    var self = this;    //保存原函数引用
    return function(){  //返回包含了原函数和新函数的 '代理函数'
        beforefn.apply(this, arguments);    //执行新函数,修正this
        return self.apply(this,arguments);  //执行原函数
    }
}
Function.prototype.after = function(afterfn) {
    var self = this;
    return function(){
        var ret = self.apply(this,arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
}
var func = function() {
    console.log('2');
}
//func1和func3为挂载函数
var func1 = function() {
    console.log('1');
}
var func3 = function() {
    console.log('3');
}
func = func.before(func1).after(func3);
func();
// 1
// 2
// 3

类装饰

@testable
class MyTestableClass {
  // ...
}
// 默认方式 (静态属性)
function testable(target) {
  target.isTestable = true;  // 
}
MyTestableClass.isTestable // true
// 实例属性
function testable(target) {
  target.prototype.isTestable = true; 
}
let obj = new MyTestableClass();
obj.isTestable  // true

// 加参
function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

适配器模式

将一个类的接口转化为另外一个接口,以满足用户需求,使类之间接口不兼容问题通过适配器得以解决。

    class Plug {
      getName() {
        return 'iphone充电头';
      }
    }

    class Target {
      constructor() {
        this.plug = new Plug();
      }
      getTypeC() {
        return this.plug.getName() + '对接 Type-c充电头 给某米充电';
      }
      getLightning() {
        return this.plug.getName() + '对接 lightning充电头 给某果充电';
      }
    }

    let target = new Target(); 
    if(!🍎){
        target.getTypeC(); // iphone充电头 对接 Type-c充电头 给某米充电
    } else {
        target.getLightning(); // iphone充电头 对接 lightning充电头 给某果充电
    }

代理模式

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

案例一 :假设当 A 在心情好的时候收到花,小明表白成功的几率有60%,而当A 在心情差的时候收到花,小明表白的成功率无限趋近于0。
小明A 刚刚认识两天,还无法辨别A 什么时候心情好。如果不合时宜地把花送给A,花被直接扔掉的可能性很大,这束花可是小明吃了7 天泡面换来的。
但是A 的朋友B 却很了解A,所以小明只管把花交给BB 会监听A 的心情变化,然后选择A 心情好的时候把花转交给A

let Flower = function() {}
let xiaoming = {
  sendFlower: function(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let B = {
  receiveFlower: function(flower) {
    A.listenGoodMood(function() {
      A.receiveFlower(flower)
    })
  }
}
let A = {
  receiveFlower: function(flower) {
    console.log('收到花'+ flower)
  },
  listenGoodMood: function(fn) {
    setTimeout(function() {
      fn()
    }, 1000)
  }
}
xiaoming.sendFlower(B)

案例二:图片懒加载的方式:先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。

var imgFunc = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();
var proxyImage = (function() {
    var img = new Image();
    img.onload = function() {
        imgFunc.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            imgFunc.setSrc('./loading,gif');
            img.src = src;
        }
    }
})();
proxyImage.setSrc('./pic.png');