前端之设计模式

37 阅读5分钟

🎉工厂模式

工厂模式就是降低耦合将重复代码进行提取。

  • 如果存在足够多的相同的属性和方法,需要进行提取,将公共的类进行包装,并且不允许实例化。
  • 父类是一个抽象类,并且不能实例
  • 子类实现自身的实例方法
function BicycleF(name){
    this.name = name
    this.method = function(){
        return this.name
    }
}
Bicycle.prototype = {
    constructor: BicycleF,
    sellBicycle:function(){
        //这里是子类的方法
        var bicycle = this.createBicycle()
        bicycle.a()
        bicycle.b()
        return bicycle
    },
    createBicycle:function(){
        throw new Error('父类不允许进行实例化,需要使用子类的方法')
    }
}    


//子类继承父类
function extend(Sub, Sup){
    var F = function(){}
    F.prototype = Suo.prototype
    Sub.prototype = new F()
    Sub.prototype.constructor = Sub
    Sub.sup = Sup.prototype
}

//创建子类

function BicycleS(name){
    BicycleF.call(this.name)
}
extend(BicycleS, BicycleF)
BicycleS.prototype.createBicycle = function(){
    var  a = function(){
        console.log("实现子类创建a")
    }
    var b = function(){
        console.log("实现子类创建b")
    }
    return {
        a,
        b
    }
}

🎉单例模式

划分命名空间,并且将属性和方法组织在一起的一种方式,只是实例化一次,并且对于实例对象为同一对象。

 function Singleton(name){
     this.name = name
 }
 Singleton.prototype.getName = function(){
     console.log(this,name)
 }
//这里是实现单例模式的方法
var getInstance = (
    function(){
    let instance
    return function(name){
        if(!instance){
          instance = new Singleton(name)
        }
     return instance
    }
 }
)()
//或者放在构造函数的静态属性上
Singleton.getInstance = function(name){
    if(!this.instance){
        this.instance = new Singleton()
    }
    return this.instance
}

 var b = getInstance('zhangsan') 
 var b = Singleton.getInstance('zhangsan') 

 var a = getInstance('lisi')

  console.log(a,b)
  console.log(a === b) // true

🎇应用

这样点击按钮只会创建一个div标签,不会重复的创建。

  //应用
  var btn = document.getElementById('btn')
     
     var creatDiv = function(){
         var div = document.createElement('div')
         div.innerText = '我是单例创建的div,只会生成一次!!'
         document.body.appendChild(div)
         div.style.display = 'none'
         return div
     }
     //这里就是单例模式的核心思想,当res本来是存在的话,就直接返回,不会再进行重新的创建。
     var creatSingleone = function(fn){
         var res
         return function(){
         //这里的this,是为了将fn的只想到btn上
             return res || (res = fn.call(this,arguments))
         }
     }

  btn.onclick = function(){
      var div = creatSingleone(creatDiv)
      div.style.display = "block"
  }

🎉策略模式

在JS中要保证开放封闭原则,对于扩展开放,对于修改封闭,降低代码的重复度。

策略模式是一种行为设计模式,定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

相当于我们只有需要对于strategies对象进行不断的配置,建立映射的关系即可,这样就可以将calculateBonus函数进行固定。

这里有篇文章讲的很棒:juejin.cn/post/711345…

//策略模式就是将现有的逻辑进行整理进行提取
//这里就相当于策略模式,可以在这个对象中进行添加,而不需要在动用底下的逻辑
var strategies = {
    S:function(salary){
        return salary * 3
    },
    A:function(salary){
        return salary * 2
    },
    B:function(salary){
        return salary * 1
    }
}

var calculateBonus = function(performanceLevel, salary){
    return strategies[performanceLevel](salary)
}
console.log(calculateBonus('S', 10000))

🎉发布订阅模式--一对多之间的关系

在发布订阅中有三个部分发布订阅模式中有三个角色,发布者 Publisher ,事件调度中心 Event Channel ,订阅者 Subscriber发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在

在Vue中自定义事件和全局事件总线就是使用的发布和订阅的模式。

class PubSub {
    constructor() {
        // 事件中心
        // 存储格式: warTask: [], routeTask: []
        // 每种事件(任务)下存放其订阅者的回调函数
        this.events = {}
    }
    // 订阅方法,就是将事件放进events
    subscribe(type, cb) {
        if (!this.events[type]) {
            this.events[type] = [];
        }
        this.events[type].push(cb);
    }
    // 发布方法,就是将events中的事件进行执行
    publish(type, ...args) {
        if (this.events[type]) {
            this.events[type].forEach(cb => cb(...args))
        }
    }
    // 取消订阅方法
    unsubscribe(type, cb) {
        if (this.events[type]) {
            const cbIndex = this.events[type].findIndex(e=> e === cb)
            if (cbIndex != -1) {
                this.events[type].splice(cbIndex, 1);
            }
        }
        if (this.events[type].length === 0) {
            delete this.events[type];
        }
    }
    unsubscribeAll(type) {
        if (this.events[type]) {
            delete this.events[type];
        }
    }
}

// 创建一个事件调度中心
let pubsub = new PubSub();

// 订阅warTask任务
pubsub.subscribe('warTask', function (taskInfo){
    console.log("信息:" + taskInfo);
})
// 订阅routeTask任务
pubsub.subscribe('routeTask', function (taskInfo) {
    console.log("信息:" + taskInfo);
});
// 订阅allTask任务
pubsub.subscribe('allTask', function (taskInfo) {
    console.log("信息:" + taskInfo);
});

// 发布任务
pubsub.publish('warTask', "这里是订阅了warTask任务的会触发");
pubsub.publish('allTask', "这里是订阅了allTask任务的会触发");
pubsub.publish('routeTask', "这里是订阅了routeTask任务的会触发");

🎉观察者模式--一对多之间的关系

  • 目标对象 Subject:

    • 维护观察者列表 observerList ———— 存放观察者列表
    • 定义添加观察者的方法
    • 当自身发生变化后,通过调用自己的 notify 方法依次通知每个观察者执行 update 方法
  • 观察者 Observer 需要实现 update 方法,供目标对象调用。update方法中可以执行自定义的业务逻辑

很有意思的小文章:juejin.cn/post/705544…

class Observer {
    constructor(name) {
        this.name = name;
    }
    update({taskType, taskInfo}) {
        // 假设任务分为日常route和战斗war
        if (taskType === "route") {
            console.log(`${this.name}不需要日常任务`);
            return;
        }
        this.goToTaskHome(taskInfo);
        
    }
    goToTaskHome(info) {
        console.log(`${this.name}去任务大殿抢${info}任务`);
    }
}

class Subject {
    constructor() {
        this.observerList = []
    }
    addObserver(observer) {
        this.observerList.push(observer);
    }
    notify(task) {
        console.log("发布五星任务");
        this.observerList.forEach(observer => observer.update(task))
    }
}

const subject = new Subject();
const stu1 = new Observer("弟子1");
const stu2 = new Observer("弟子2");

// stu1 stu2 购买五星任务通知权限
subject.addObserver(stu1);
subject.addObserver(stu2);

// 任务殿发布五星战斗任务
const warTask = {
    taskType: 'war',
    taskInfo: "猎杀时刻"
}

// 任务大殿通知购买权限弟子
subject.notify(warTask);

// 任务殿发布五星日常任务
const routeTask = {
    taskType: 'route',
    taskInfo: "种树浇水"
}

subject.notify(routeTask);

🎉适配器模式

在使用一个已经存在的类,但是如果他的接口实现的方法不满足你的要求不同时,就可以使用适配器模式。其实站在软件设计的角度,在开发前期应该考虑好,而不是一开始就使用适配器模式来实现,这样是不合理的。

适配器模式往往存在于软件开发后期两个不兼容接口,并且不易修改,可以使用适配器模式来解决。

var getBeijingMap = function(){
    var Beijing = [
        {name:'朝阳', id:1},
        {name:'海淀', id:12}
    ]
    return Beijing
}

var render = function(fn){
    console.log(JSON.stringify(fn()))
}

render(getBeijingMap)
//[{"name":"朝阳", "id":"11"},{"name":"海淀"},"id":"12"]

//当我需要另外一种格式并且不能进行修改原来的代码时
//这里就是适配器
var addressAdapter function(oldAdress){
    var adress = {},oldAdress = oldAdress(), item
    or(var i = 0; i < oldAdress.length; i++){
        item = oldAdress[i]
        adress[item.name] = item.id
    }
    return function(){
        return adress
    }
}
render(addressAdapter(getBeijingMap))

//{"朝阳":"11","海淀":"12"}