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>

posted @ 2022-02-13 13:08  李小晚  阅读(23)  评论(0)    收藏  举报