最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 精准而优雅的设计模式

    正文概述 掘金(耶巴蒂厦门)   2021-01-21   408

    构造器

    名字吓人。结果天天使用。

    • 场景: 公司员工信息录入系统、少量员工录入
    const lilei = {
      name: '李磊',
      age: 25,
      career: 'coder'
    }
    // 构造器方式
    function User(name, age, career) {
      this.name = name;
      this.age = age;
      this.career = career;
    }
    const lilei = New User('李磊', 25, 'coder');
    
    • 程序自动地去读取数据库里面一行行的员工信息,然后把拿到的姓名、年龄职业等字段塞进User函数里,进行一个简单的调用。

    • 构造器是不是将 name、age、career 赋值给对象的过程封装,确保了每个对象都具备这些属性,确保了共性的不变,同时将 name、age、career 各自的取值操作开放

    简单工厂模式

    • 场景: 区分员工的职业。如果是码农就写Bug。如果老板就会所。
    // 构造器方式
    function User(name, age, career, work) {
      this.name = name;
      this.age = age;
      this.career = career;
      this.work = work;
    }
    function Factory(name, age, career){
      let work;
      swtich(career){
        case 'coder':
        	work = '写Bug';
        	break;
        case 'boss':
        	work = '会所';
        	break;
        default:
        	break;
      }
     	return new User(name, age, career, work)
    }
    
    const pro = new Factory('pro', 18, 'book');
    

    总结: 工厂模式的简单之处,在于它的概念相对好理解:将创建对象的过程单独封装,这样的操作就是工厂模式。同时它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new、自觉非常不爽的情况下,我们就应该思考是不是可以掏出工厂模式重构我们的代码了

    单例模式

    • 只有一个实例
    class Modal{
    		static getModal(){
    			if(!Modal.modal){
    					Modal.modal  = 123;
    			}
    			return Modal.modal;
    		}
    }
    const modal1 = Modal.getModal();
    const modal2 = Modal.getModal();
    modal1 === modal2; // true
    
    • 场景: UI框架其中的modal只有一个实例
    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="UTF-8">
        <title>单例模式弹框</title>
    </head>
    <style>
        #modal {
            width: 200px;
            height: 200px;
            line-height: 200px;
            text-align: center;
            border-radius: 10px;
            background-color: #f2f2f2;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    </style>
    <body>
    <div class="btnBox">
        <button id='open'>打开弹框</button>
        <button id='close'>关闭弹框</button>
    </div>
    
    </body>
    <script>
        // 闭包方式
        // const Modal = (function () {
        //     let modal = null;
        //     return function() {
        //         if (!modal){
        //             modal = document.createElement('div');
        //             modal.id = 'modal';
        //             modal.style.display = 'none';
        //             modal.innerHTML = "唯一弹窗";
        //             document.body.appendChild(modal);
        //
        //         }
        //         return modal;
        //     }
        // })();
    
        // class方式
        class Modal {
            static getModal() {
                if (!Modal.modal) {
                    Modal.modal = document.createElement('div');
                    Modal.modal.id = 'modal';
                    Modal.modal.style.display = 'none';
                    Modal.modal.innerHTML = '唯一弹窗';
                    document.body.appendChild(Modal.modal);
                }
                return Modal.modal;
            }
        }
    
        // 点再多下也只是有Modal
        document.getElementById('open').addEventListener('click', function () {
            const modal = Modal.getModal();
            modal.style.display = 'block';
        });
        // 点击关闭按钮隐藏模态框
        document.getElementById('close').addEventListener('click', function () {
            const modal = Modal.getModal();
            modal.style.display = 'none';
        });
    </script>
    </html>
    

    原型模式

    • 原型是 把所有的对象共用的属性全部放在堆内存的一个对象中(共用属性组成的对象),然后让每一个对象的__proto__存储这个(共用属性组成的对象)的地址。而这个共用属性就是原型。原型出现的目的就是为了减少不必要的内存消耗。

    • 原型链就是对象通过__proto__向当前实例所属类的原型上查找属性或方法的机制,如果找到Object的原型上还是没有找到想要的属性或者是方法则查找结束,最终会返回undefined,终点是null。

    • 原型模式不仅是一种设计模式,它还是一种编程范式,是 JavaScript 面向对象系统实现的根基。

    function Dog() {
    }
    // 原型增加属性和方法
    Dog.prototype.name = 'pro';
    Dog.prototype.eat = () => {
      console.log(123);
    }
    

    装饰器模式

    只添加,不修改就是装饰器模式了

    • 场景: 初始需求是每个业务中的按钮在点击后都弹出「您还未登录哦」的弹框。
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>按钮点击需求1.0</title>
    </head>
    <style>
        #modal {
            height: 200px;
            width: 200px;
            line-height: 200px;
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            border: 1px solid black;
            text-align: center;
        }
    </style>
    <body>
    	<button id='open'>点击打开</button>
    	<button id='close'>关闭弹框</button>
    </body>
    <script>
        // 弹框创建逻辑,这里我们复用了单例模式面试题的例子
        const Modal = (function() {
        	let modal = null
        	return function() {
                if(!modal) {
                	modal = document.createElement('div')
                	modal.innerHTML = '您还未登录哦~'
                	modal.id = 'modal'
                	modal.style.display = 'none'
                	document.body.appendChild(modal)
                }
                return modal
        	}
        })()
        
        // 点击打开按钮展示模态框
        document.getElementById('open').addEventListener('click', function() {
            // 未点击则不创建modal实例,避免不必要的内存占用
        	const modal = new Modal()
        	modal.style.display = 'block'
        })
        
        // 点击关闭按钮隐藏模态框
        document.getElementById('close').addEventListener('click', function() {
        	const modal = document.getElementById('modal')
        	if(modal) {
        	    modal.style.display = 'none'
        	}
        })
    </script>
    </html>
    
    • 突然修改需求:弹框被打开后把按钮的文案改为“快去登录”,同时把按钮置灰。存在几百按钮且同时他们不是组件且有复杂业务情况下。不去关心它现有的业务逻辑是啥样的。对它已有的功能做个拓展,只关心拓展出来的那部分新功能如何实现

    为了不被已有的业务逻辑干扰,当务之急就是将旧逻辑与新逻辑分离,把旧逻辑抽出去

    // 将展示Modal的逻辑单独封装
    function openModal() {
        const modal = new Modal()
        modal.style.display = 'block'
    }
    // 新增逻辑
    // 按钮文案修改逻辑
    function changeButtonText() {
        const btn = document.getElementById('open')
        btn.innerText = '快去登录'
    }
    
    // 按钮置灰逻辑
    function disableButton() {
        const btn =  document.getElementById('open')
        btn.setAttribute("disabled", true)
    }
    
    // 新版本功能逻辑整合
    function changeButtonStatus() {
        changeButtonText()
        disableButton()
    }
    
    document.getElementById('open').addEventListener('click', function() {
        openModal()
        changeButtonStatus()
    })
    
    • 使用ES6面向对象写法
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>按钮点击需求1.0</title>
    </head>
    <style>
        #modal {
            height: 200px;
            width: 200px;
            line-height: 200px;
            position: fixed;
            left: 50%;
            top: 50%;
            border-radius: 10px;
            transform: translate(-50%, -50%);
            border: 1px solid black;
            text-align: center;
        }
    </style>
    <body>
    <button id='open'>点击打开</button>
    <button id='close'>关闭弹框</button>
    </body>
    <script>
        // 弹框创建逻辑,这里我们复用了单例模式面试题的例子
        const Modal = (function() {
            let modal = null;
            return function() {
                if(!modal) {
                    modal = document.createElement('div');
                    modal.innerHTML = '您还未登录哦~';
                    modal.id = 'modal';
                    modal.style.display = 'none';
                    document.body.appendChild(modal)
                }
                return modal
            }
        })();
    
        // 定义打开按钮
        class OpenButton {
            onClick() {
                const modal = new Modal();
                modal.style.display = 'block';
            }
        }
        // 定义按钮对应的装饰器
        class Decorator{
            // 将按钮传入
            constructor(open_button) {
                this.open_button = open_button;
            }
            onClick(){
                this.open_button.onClick();
                this.changeButtonStatus();
            }
            changeButtonStatus() {
                this.disableButton();
                this.changeButtonText();
            }
            disableButton() {
                const btn = document.getElementById('open');
                btn.setAttribute('disabled', true);
            }
            changeButtonText() {
                const btn = document.getElementById('open');
                btn.innerText = '快去登录'
            }
        }
    
        // 点击打开按钮展示模态框
        document.getElementById('open').addEventListener('click', function() {
            // 未点击则不创建modal实例,避免不必要的内存占用
            const openButton = new OpenButton();
            const decorator = new Decorator(openButton);
            decorator.onClick();
        });
    
        // 点击关闭按钮隐藏模态框
        document.getElementById('close').addEventListener('click', function() {
            const modal = document.getElementById('modal');
            if(modal) {
                modal.style.display = 'none'
            }
        })
    </script>
    </html>
    

    适配模式

    适配器模式通过把一个类的接口变换成客户端所期待的另一种接口,可以帮我们解决不兼容的问题。

    • 场景: iPhoneX没有圆头耳机孔、转接头是个适配模式。

    把一个(iPhone X)的接口(方形)变换成客户端(用户)所期待的另一种接口(圆形)

    • axios能在网页和Nodejs不同环境使用就是使用了适配模式

    代理模式

    代理模式,式如其名——在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式

    ![image-20210121162225864](/Users/DJVue/Library/Application Support/typora-user-images/image-20210121162225864.png)

    代理服务器 = 代理模式

    ![image-20210121162237935](/Users/DJVue/Library/Application Support/typora-user-images/image-20210121162237935.png)

    • 事件代理也是代理模式的一种

    事件代理,可能是代理模式最常见的一种应用方式,也是一道实打实的高频面试题。它的场景是一个父元素下有多个子元素。

    需求: 点击每个 a 标签,都可以弹出“我是xxx”这样的提示。比如点击第一个 a 标签,弹出“我是链接1号”这样的提示

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>事件代理</title>
    </head>
    <body>
    <div id="father">
        <a href="#">链接1号</a>
        <a href="#">链接2号</a>
        <a href="#">链接3号</a>
        <a href="#">链接4号</a>
        <a href="#">链接5号</a>
        <a href="#">链接6号</a>
    </div>
    <script>
        const father = document.getElementById('father');
        father.addEventListener('click',(e) => {
            if (e.target.tagName === 'A'){
                e.preventDefault();
                alert(`我是${e.target.innerText}`);
            }
        })
    </script>
    </body>
    </html>
    

    策略模式

    需求:

    • 当价格类型为“预售价”时,满 100 - 20,不满 100 打 9 折
    • 当价格类型为“大促价”时,满 100 - 30,不满 100 打 8 折
    • 当价格类型为“返场价”时,满 200 - 50,不叠加
    • 当价格类型为“尝鲜价”时,直接打 5 折

    转成字段

    预售价 - pre
    大促价 - onSale
    返场价 - back
    尝鲜价 - fresh
    

    当初的我对prd处理为:

    // 询价方法,接受价格标签和原价为入参
    function askPrice(tag, originPrice) {
    
      // 处理预热价
      if(tag === 'pre') {
        if(originPrice >= 100) {
          return originPrice - 20
        } 
        return originPrice * 0.9
      }
      
      // 处理大促价
      if(tag === 'onSale') {
        if(originPrice >= 100) {
          return originPrice - 30
        } 
        return originPrice * 0.8
      }
      
      // 处理返场价
      if(tag === 'back') {
        if(originPrice >= 200) {
          return originPrice - 50
        }
        return originPrice
      }
      
      // 处理尝鲜价
      if(tag === 'fresh') {
         return originPrice * 0.5
      }
    }
    

    现在的我会采用策略模式:

    // 定义一个询价处理器对象
    const priceProcessor = {
      pre(originPrice) {
        if (originPrice >= 100) {
          return originPrice - 20;
        }
        return originPrice * 0.9;
      },
      onSale(originPrice) {
        if (originPrice >= 100) {
          return originPrice - 30;
        }
        return originPrice * 0.8;
      },
      back(originPrice) {
        if (originPrice >= 200) {
          return originPrice - 50;
        }
        return originPrice;
      },
      fresh(originPrice) {
        return originPrice * 0.5;
      },
    };
    
    // 询价函数
    function askPrice(tag, originPrice) {
      return priceProcessor[tag](originPrice)
    }
    // 如要增加需求
    priceProcessor.newUser = function (originPrice) {
      if (originPrice >= 100) {
        return originPrice - 50;
      }
      return originPrice;
    }
    

    一个函数只做一件事、遇到 Bug 时,就可以做到“头痛医头,脚痛医脚”,而不必在庞大的逻辑海洋里费力去定位到底是哪块不对。

    策略模式就是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换

    状态模式

    原理跟策略模式相似。故不展开

    观察者模式

    观察者模式,是所有 JavaScript 设计模式中使用频率最高,面试频率也最高的设计模式,所以说它十分重要——如果我是面试官,考虑到面试时间有限、设计模式这块不能多问,我可能在考查你设计模式的时候只会问观察者模式这一个模式。该模式的权重极高,我们此处会花费两个较长的章节把它掰碎嚼烂了来掌握。

    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

    // 定义发布者类
    class Publisher {
      constructor() {
        this.observers = []
        console.log('Publisher created')
      }
      // 增加订阅者
      add(observer) {
        console.log('Publisher.add invoked')
        this.observers.push(observer)
      }
      // 移除订阅者
      remove(observer) {
        console.log('Publisher.remove invoked')
        this.observers.forEach((item, i) => {
          if (item === observer) {
            this.observers.splice(i, 1)
          }
        })
      }
      // 通知所有订阅者
      notify() {
        console.log('Publisher.notify invoked')
        this.observers.forEach((observer) => {
          observer.update(this)
        })
      }
    }
    // 定义订阅者类
    class Observer {
        constructor() {
            console.log('Observer created')
        }
    
        update() {
            console.log('Observer.update invoked')
        }
    }
    
    • 面试题: Vue双向绑定
    // observe方法遍历并包装对象属性
    function observe(target) {
        // 若target是一个对象,则遍历它
        if(target && typeof target === 'object') {
            Object.keys(target).forEach((key)=> {
                // defineReactive方法会给目标属性装上“监听器”
                defineReactive(target, key, target[key])
            })
        }
    }
    
    // 定义defineReactive方法
    function defineReactive(target, key, val) {
        // 属性值也可能是object类型,这种情况下需要调用observe进行递归遍历
     		const dep = new Dep()
        observe(val)
        // 为当前属性安装监听器
        Object.defineProperty(target, key, {
             // 可枚举
            enumerable: true,
            // 不可配置
            configurable: false, 
            get: function () {
                return val;
            },
            // 监听器函数
            set: function (value) {
                // 通知所有订阅者
                dep.notify()
            }
        });
    }
    
    // 定义订阅者类Dep
    class Dep {
        constructor() {
            // 初始化订阅队列
            this.subs = []
        }
        
        // 增加订阅者
        addSub(sub) {
            this.subs.push(sub)
        }
        
        // 通知订阅者(是不是所有的代码都似曾相识?)
        notify() {
            this.subs.forEach((sub)=>{
                sub.update()
            })
        }
    }
    

    观察者模式与发布-订阅模式的区别是什么?

    所有的开发者拉了一个群,直接把需求文档丢给每一位群成员,这种发布者直接触及到订阅者的操作,叫观察者模式。但如果把需求文档上传到了公司统一的需求平台上,需求平台感知到文件的变化、自动通知了每一位订阅了该文件的开发者,这种发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式

    迭代器模式

    迭代器模式是设计模式中少有的目的性极强的模式: 遍历

    • 任何数据结构只要具备Symbol.iterator属性,就可以被遍历
    // 编写一个迭代器生成函数
    function *iteratorGenerator() {
        yield '1号选手'
        yield '2号选手'
        yield '3号选手'
    }
    
    const iterator = iteratorGenerator()
    
    iterator.next()
    iterator.next()
    iterator.next()
    

    起源地下载网 » 精准而优雅的设计模式

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元