设计模式--js(二十三)

89 阅读5分钟

设计模式

  • 设计模式:
    • 为了实现某一类功能给出的简洁而优化的解决方案

一、单例模式

1、单例模式

  • 核心意义: 一个构造函数(类) 一生只有一个实例化对象

2、思路:

  • 实例化一个类的时候
  • 需要先判断, 如果当前类以前被实例化过, 不需要再次实例化了
  • 而是直接拿到原先的实例化对象
  • 如果没有被实例化过, 那么新实例化一个实例对象

3、解决:

  • 单独找一个变量, 初始化为 null
  • 每一次创建实例的时候, 都判断一下该变量的值
    • 如果为 null, 那么创建一个实例, 赋值给该变量
    • 如果不为 null, 那么我们不再创建实例, 直接给出该变量的值

        class Dialog {
            constructor () {
                console.log('进行了一次实例化')
                this.text = '我是Dialog'
            }
        }


        let obj = null
        function newDialog () {
            if (obj === null) {
                obj = new Dialog()
            }

            return obj
        }
        //第一次调用,创建一个实例化对象然后给到obj中,并返回obj
        const d1 = newDialog()
        console.log(d1)//Dialog {text: '我是Dialog'}
        //第二次调用,直接返回obj
        const d2 = newDialog()
        console.log(d2)//Dialog {text: '我是Dialog'}


        console.log(d1 === d2)//true

二、单例模式变形

1、单例模式问题

1. 向外部写了一个全局变量 obj
2. 多次调用时, 无法传递不一样的参数
3. 构造函数的类名与创建实例的函数名是两个东西

2、解决:

  1. 利用闭包

    let newDialog = (function fn() {
        let obj = null;
        return function () {
            if (obj == null) {
                obj = new Dialog();
            }
            return obj;
        };
    })();
    
  2. 创建一个 init 方法

    class Dialog {
        constructor() {
            console.log("在 页面中 创建了一个 弹出层, 文本为: ");
            this.title = "";
        }
        setTitle(newTitle) {
            this.title = newTitle;
            console.log("将 title 属性 重新修改为了: ", this.title);
        }
    }
    let newDialog = (function outer() {
        let obj = null;
        return function inner(str) {
            // 没有接收到不同参数的原因在于, 这个 if 只有第一次才会执行, 后续永远不会执行
            if (obj == null) {
                obj = new Dialog();
            }
            // 但是, 这个位置却可以多次执行, 所以我们可以在这里, 给这个 类传递不同的参数
            obj.setTitle(str);
            return obj;
        };
    })();
    
  3. 将类放在闭包函数内, 然后将函数名修改为类名

    let Dialog = (function outer() {
        // 因为 Dialog 这个 类, 永远就使用一次, 所以函数局部 更合适
        class Dialog {
            constructor() {
                console.log("在 页面中 创建了一个 弹出层, 文本为: ");
                this.title = "";
            }
            setTitle(newTitle) {
                this.title = newTitle;
                console.log("将 title 属性 重新修改为了: ", this.title);
            }
        }
    
        let obj = null;
        return function inner(str) {
            if (obj == null) {
                obj = new Dialog();
            }
            obj.setTitle(str);
            return obj;
        };
    })();
    

3、变形过程(课堂)

1)基本版

      class Dialog {
            constructor() {
                this.text = '默认文本'
                console.log('实例化了一次 Dialog')
            }
        }

        let instance = null
        function newDialog () {
            if (instance === null) {
                instance = new Dialog()
            }

            return instance
        }

        const d1 = newDialog()
        const d2 = newDialog()

        console.log(d1)
        console.log(d2)
        console.log(d1 === d2)

2)把全局变量处理掉

    class Dialog {
            constructor() {
                this.text = '默认文本'
                console.log('实例化了一次 Dialog')
            }
        }
        // 利用闭包解决了一个全局变量
        const newDialog = (() => {
            let instance = null
            return function () {
                if (instance === null) {
                    instance = new Dialog()
                }
                return instance
            }
        })()
        const d1 = newDialog()
        const d2 = newDialog()
        console.log(d1)
        console.log(d2)
        console.log(d1 === d2)

3)解决每一次调用类的时候, 无法修改类内部的属性

     class Dialog {
            constructor() {
                this.text = '默认文本'
                console.log('实例化了一次 Dialog')
            }
            setText (newText) {
                this.text = newText
            }
        }
        const newDialog = (() => {
            let instance = null
            return function (text) {
                if (instance === null) {
                    /**
                     *  在单例模式中, 当前 分支 只会在 首次调用的时候执行
                     *  后续的时候调用 分支是不会执行的
                    */
                    instance = new Dialog()
                }
                // 当前位置之后, 每一次调用 newDialog 函数 都会执行
                instance.setText(text)
                return instance
            }
        })()
       const d1 = newDialog('我是老大')
        console.log(d1);//我是老大
        const d2 = newDialog('我是老二')
        console.log(d2);//我是老二
        const d3 = newDialog('我是老三')
        console.log(d3);//我是老三
        console.log(d1)//我是老三--因为d1 === d2 === d3 === instance)


        console.log(d3 === d2)//true
        console.log(d3 === d1)//true

4)解决了Dialog类名冲突问题

     const Dialog = (() => {

            let instance = null

            class Dialog {
                constructor() {
                    this.text = '默认文本'
                    console.log('实例化了一次 Dialog')
                }

                setText(newText) {
                    this.text = newText
                }
            }

            return function (text) {
                if (instance === null) {
                    instance = new Dialog()
                }
                instance.setText(text)
                return instance
            }

        })()

        const d1 = Dialog('只有第一次有用')
        console.log(d1)

        const d2 = new Dialog('警告文本')
        console.log(d2)

        const d3 = new Dialog('成功文本')
        console.log(d3)

三、策略模式

  • 设计模式 - 策略模式
    • 核心: 减少 if...else...
    • 作用: 把多种形式的内容罗列出来
  • 案例:
    • 已知一个购物总价 1376
    • 需要根据折扣计算最终价格
    • 折扣种类: 8 折, 7 折, 300-30, 500-50
  • 当前案例核心:
    • 用一个数据结构, 把各种折扣记录下来
    • 值存储的就是该折扣和总价的计算方式
const calcPrice = (function () {
    const calcList = {
        "80%": (total) => (total * 0.8).toFixed(2),
        "70%": (total) => (total * 0.7).toFixed(2),
    };
    // 任何引用数据类型, 都可以当作 对象去使用
    function inner(type, total) {
        return calcList[type](total);
    }
    inner.add = function (type, fn) {
        calcList[type] = fn;
    };
    inner.sub = function (type) {
        delete calcList[type];
    };
    inner.getList = function () {
        return calcList;
    };
    return inner;
})();
const res = calcPrice("80%", 1000);
console.log(res);

const res1 = calcPrice("70%", 1000);
console.log(res1);

calcPrice.add("300-30", (total) => total - Math.floor(total / 300) * 30);

const res2 = calcPrice("300-30", 1000);
console.log(res2);

calcPrice.sub("70%");
console.log(calcPrice.getList());

四、发布订阅模式

  • 设计模式-发布订阅
    • 首先明确 这个模式和一个叫做观察者模式类似但不是一个东西
    • 目前一批开发人员认为这两个是一个东西, 另外一批认为是两个东西
  • 案例: 买一本书
    • 以前
      1. 去到书店, 问店员有没有 JS 从入门到入土
      2. 没有, 回去
      3. 一会再回去问, 有没有
      4. 没有, 回去
      5. 一会再回去问, 有没有
      6. ...
      7. 一会再回去问, 有没有
      8. 有了, 购买
    • 现在
      1. 去到书店, 问店员有没有 JS 从入门到入土
      2. 没有, 给店员留下一个联系方式
      3. 直到店员给你打电话, 你触发技能(去买回来)
  • 在 JS 内 发布订阅 使用最多的地方 (addEventListener)
    • xxx.addEventListener('click', fn)
  • 思考: 以什么数据格式来记录
店员 ===
    {
        a: [fn1, fn2],
        b: [fn3, fn4],
    };
class Observer {
    constructor(name) {
        this.name = name; // 这步模拟店员名字, 可以不要
        // 店员的记录手册
        this.messages = {};
    }
    add(type, fn) {
        if (this.messages[type] === undefined) this.messages[type] = [];

        this.messages[type].push(fn);
    }
    remove(type, fn) {
        if (this.messages[type] === undefined) return;

        // if (fn === undefined) return this.messages[type] = []
        if (fn === undefined) return delete this.messages[type];

        this.messages[type] = this.messages[type].filter((item) => item !== fn);
    }
    trigger(type) {
        // console.log(type)
        // console.log(this.messages[type])
        this.messages[type].forEach((item) => item());
    }
}

const ob = new Observer("小李");

const A1 = () => {
    console.log("我是 张三, 我要买这本书");
};
const A2 = () => {
    console.log("我是 李四, 我要买这本书");
};
const B1 = () => {
    console.log("我是 王五, 我要买这本书");
};

ob.add("A1", A1);
ob.add("A1", A2);
ob.add("B1", B1);

// ob.remove('A1', A1)
// ob.remove('A1')
ob.remove("C1");

ob.trigger("A1");

console.log(ob);