JS设计模式分类有:工厂设计模式,单例设计模式,观察者模式,策略模式,模板模式和命令模式

一、工厂设计模式

工厂设计模式分为简单工厂设计模式和复杂工厂设计模式

简单工厂设计模式

给定不同的材料,生产不同属性值的产品,并把产品返回;批量生产,具备相同属性的产品。

<script>
        //咖啡机
        function makeCoffee(dou, water) {
            var obj = new Object();
            obj.dou = dou;
            obj.water = water;
            obj.bili = dou / water;
            return obj;
        }
        var coffee = makeCoffee(1, 10);
        console.log('造出了纯度为', coffee.bili, ' 的咖啡');
        var coffee2 = makeCoffee(5, 10);
        console.log('造出了纯度为', coffee2.bili, ' 的咖啡');
    </script>

复杂工厂设计模式

可以根据不同类型来创建不同的产品

<script>
        //工厂里面含有生产线
        //果汁工厂
        function FruitMake() {

        }
        FruitMake.prototype.make = function (type, meta) {
                //1.先判断是否有对应生产能力
                if(typeof this[type] ==='function'){
                    var func = this[type];
                    func.prototype = FruitMake.prototype;
                    return new func(meta);
                }else{
                    throw '很抱歉,工厂不能生产这个产品';
                }
        };
        //扩展生产线
        FruitMake.prototype.extend = function (obj) {
            for (var key in obj) {
               this[key] = obj[key];
            }
        };
        //苹果汁和梨子汁
        FruitMake.prototype.extend({
            Apple:function (meta) {  
                console.log('造出了一杯苹果汁,材料有',meta);
            },
            Pear:function (meta) {  
                console.log('造出了一杯梨子汁,材料有',meta);
            }
        })
        //1.实例化
        var maker = new FruitMake();

        var appleObj = maker.make('Apple','一个苹果,一杯水');
        console.log(appleObj);
    </script>

二、单例设计模式

在整个程序运行的过程中一个类型只有一个实例对象。通过指定的构造函数,无论创建多少次对象,都只有一个。

实现单例设计模式的方法

  1. 全局变量
  2. 静态属性
  3. 即时函数
  4. 惰性函数

1. 全局变量

步骤

  1. 提供—个全局的变量
  2. 提供一个构造函数
  3. 在构造函数内部先判断全局变量是否有值,如果有那么就直接返回
  4. 如果没有,那么就把this成值给全局变量
  5. 通过this设置属性和方法

缺点

使用一个全局变量来保存单例对象.该全局变量在整个作用域中都可以被访问或者是修改可能会轻易的被覆盖或者是修改。修改之后,创建出来的实例对象就不再是之前的那个单例对象了。

<script>
    var instance = null;
    function Tool() {
        //1.判断instance是否有值,有就返回
        if (instance) {
            return instance;
        }
        //2.指向
        instance = this;
        this.name = '王二';
        this.intro = '热爱';
    }
    //3.实例化
    var t1 = new Tool();
    var t2 = new Tool();
    var t3 = new Tool();
    console.log(t1 === t2); //true
    console.log(t1 === t3); //true
</script>

2. 静态属性

静态属性实现所存在的问题和全局变量一样,也容易通过构造函数来修改实例对象。

3. 即时函数

<script>
    (function (w) {
        var instance = null;

        function Tool() {
            //1.判断instance是否有值,有就返回
            if (instance) {
                return instance;
            }
            //2.指向
            instance = this;
            this.name = '王二';
            this.intro = '凡夫俗子';
        }

        w.Tool = Tool;
    })(window);
    //3.实例化
    Tool.prototype.run = function () {
        console.log('跑');
    }
    var t1 = new Tool();
    var t2 = new Tool();
    var t3 = new Tool();
    console.log(t1 === t2); //true
    console.log(t1 === t3); //true
    t1.run();
    t2.run();
    t3.run();
</script>

4. 惰性函数

步骤

  1. 提供构造函数
  2. 在构造函数内部提供一个私有变量(instance)
  3. 使用惰性函数定义来更新构造函数(直接把私有变量的值返回)
  4. 使用this设置属性和方法
  5. this赋值给私有变量
<script>
    function Tool() {
        var instance = this;
        var oldPrototype = Tool.prototype;
        this.name = '张三';
        this.age = 18;
        //惰性函数(只会被执行一次,后面直接被调用)
        Tool = function () {
            return instance;
        };
        Tool.prototype = oldPrototype;
        instance = new Tool();
        instance.constructor = Tool;
        return instance;
    }

    var t1 = new Tool();
    var t2 = new Tool();
    Tool.prototype.run = function () {
        console.log('跑');
    }
    t1.run();
    t2.run();
</script>

三、观察者模式

即发布-订阅模式,用户必须订阅才能接收到发布者的消息。三要素:发布者,订阅者和消息发布途径。而对于订阅者来说有时也要分不同类型,给对应的类型发布对应的消息。

3.1 不同用户订阅同一类型消息

以给同一类型的用户发布消息为例

<script>
        var lk = {
            //用户群体(对象数组)
            targetActions: [],
            addUser: function (target, action) {
                //拿到传入的对象并存入targetActions中
                var obj = {
                    target: target,
                    action: action
                };
                this.targetActions.push(obj);
            },
            publishMsg: function () {
                //遍历对象数组
                for (var i = 0; i < this.targetActions.length; i++) {
                    //取出单独的对象
                    var obj = this.targetActions[i];
                    var target = obj.target;
                    var action = obj.action;
                    //target借用action的方法(即response)
                    //输出的格式是  '已推送' this.name msg
                    action.call(target, 'msg');
                }
            }
        }

        function response(msg) {
            console.log('已推送', this.name, msg);
        }
        var stu1 = {
            name: '张三'
        };
        var stu2 = {
            name: '李四'
        };
        lk.addUser(stu1, response);
        lk.addUser(stu2, response);
        lk.publishMsg();
    </script>

3.2 同一用户订阅不同类型消息

以给不同学科的学生发布通知为例

<script>
        var lk = {
            typeTargetActions: {},
            //1.添加用户
            addUser: function (type, target, action) {
                //1.1判断有没有该类型
                if (typeof this.typeTargetActions[type] === "undefined") {
                    this.typeTargetActions[type] = [];
                }
                //1.2创建对象,存入对应type的对象数组
                var obj = {
                    target: target,
                    action: action
                };
                this.typeTargetActions[type].push(obj);
            },
            //2.发布消息
            publishMsg: function (type, msg) {
                //2.1获取对应学科的对象数组
                var targetActions = this.typeTargetActions[type] || [];
                //2.2遍历数组
                for (var i = 0; i < targetActions.length; i++) {
                    //2.3取出每个对象
                    var obj = targetActions[i];
                    var target = obj.target; //
                    var action = obj.action;
                    action.call(target, msg);
                }

            },

        };
        var stu1 = {
            name: '张三'
        };
        var stu2 = {
            name: '李四'
        };
        lk.addUser('h5', stu1, function (msg) {
            console.log(msg, '已经发送给', this.name);
        });

        lk.addUser('java', stu1, function (msg) {
            console.log(msg, '已经发送给', this.name);
        });
        lk.addUser('h5', stu2, function (msg) {
            console.log(msg, '已经发送给', this.name);
        });
        lk.addUser('python', stu2, function (msg) {
            console.log(msg, '已经发送给', this.name);
        });

        //广播消息
        lk.publishMsg('h5', 'H5学院:今晚不上课了!');
        lk.publishMsg('java', 'java学院:今晚要上课了!');
    </script>

四、策略模式

对于同一个问题,可能会有不同的策略来解决。可以根据情况来选择最佳的策略。

举例:一个小球从一个地点运动至另一个地点。可能会匀速运动,可能慢速运动,可能快速运动…

<script>
        var Celue = {
            slow: function (distance) {
                console.log('慢速运动策略,耗时:', distance * 2 + '小时');
            },
            normal: function (distance) {
                console.log('正常运动策略,耗时:', distance + '小时');
            },
            fast: function (distance) {
                console.log('慢速运动策略,耗时:', distance / 2 + '小时');
            },

        }

        function PersonRun(from, to) {
            this.from = from;
            this.to = to;
        }
        PersonRun.prototype.run = function (celue) {
            celue(this.to - this.from);
        };
        var person = new PersonRun(0, 20);
        person.run(Celue.slow);
        person.run(Celue.normal);
        person.run(Celue.fast);
    </script>

五、模板模式

很多事情的流程步骤都是一模一样,只是部分细节不同。

此时可以在父类型定义这个模板,封装这些固定的操作->子类型重写部分方法

举例:冲水果汁

<script>
        function Fruit(params) {

        }
        Fruit.prototype.make = function () {
            //1.烧开水
            this.water();
            //2.放入材料
            this.cailiao();
            //3.搅拌
            this.jiaoban();
            //4.等凉就可以喝
            this.liangliang();
        };
        Fruit.prototype.water = function () {
            console.log('烧好水,倒开水');
        };
        Fruit.prototype.cailiao = function () {
            throw new Error('必须由子类重写该方法!');
        };
        Fruit.prototype.jiaoban = function () {
            console.log('搅拌完成!');
        };
        Fruit.prototype.liangliang = function () {
            console.log('凉一下,就可以喝了!');
        };
        //借用构造函数继承
        function Apple() {};
        Apple.prototype = new Fruit();
        Apple.prototype.constructor = Apple;
        Apple.prototype.cailiao = function () {
            console.log('放入苹果');
        };
        var apple = new Apple();
        apple.make();
        console.log(apple);
    </script>

六、命令模式

优点:

  1. 降低对象之间的耦合度
  2. 新的命令可以很容易地加入到系统中
  3. 调用同一方法实现不同的功能

举例:顾客点餐

<!--
         点餐人员,关注对象:菜单
         厨房老大,关注:分配
         厨师,关注:菜单

         即业务分离,低耦合
     -->
    <script>
        //厨师
        var cook1 = {
            name: '王小二',
            make: function (foodType) {
                switch (foodType) {
                    case 'potato':
                        console.log(this.name, '做土豆');
                        break;
                    case 'egg':
                        console.log(this.name, '做鸡蛋');
                        break;
                    case 'tomato':
                        console.log(this.name, '做番茄');
                        break;
                    default:
                        console.log('不会做');
                        break;
                }
            },
        };
        var cook2 = {
            name: '李小四',
            make: function (foodType) {
                switch (foodType) {
                    case 'potato':
                        console.log(this.name, '做土豆加酱油');
                        break;
                    case 'egg':
                        console.log(this.name, '做鸡蛋加辣椒');
                        break;
                    case 'tomato':
                        console.log(this.name, '做番茄加糖');
                        break;
                    default:
                        console.log('不会做');
                        break;
                }
            },
        };

        //服务员,帮助客人点餐
        var foodList = ['potato', 'egg', 'tomato'];
        //点餐系统
        function MakeFoodCommand(cook, foodType) {
            this.cook = cook;
            this.foodType = foodType;

        }
        MakeFoodCommand.prototype.execute = function () {
            this.cook.make(this.foodType);
        };

        //做菜命令
        var commands = [];
        for (var i = 0; i < foodList.length; i++) {
            var command = null;
            if (i % 2 === 0) {
                command = new MakeFoodCommand(cook1, foodList[i]);
            } else {
                command = new MakeFoodCommand(cook2, foodList[i]);
            }
            commands.push(command);
        }

        // console.log(commands);
        commands.forEach(function (cmd) {
            console.log(cmd);
            cmd.execute();
        });
    </script>