Menu

js重点知识总结—设计模式

假设有一个空房间,我们要日复一日地往里面放一些东西。最简单的办法当然是把这些东西直接扔进去,但是时间久了,就会发现很难从这个房子里找到自己想要的东西,要调整某几样东西的位置也不容易。所以在房间里做一些柜子也许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用这些柜子存放东西的规则,就是一种设计模式。

设么是设计模式?找出 程序中变化的地方,并将变化封装起来。这种封装就是设计模式。它的关键是意图,而不是结构。学习设计模式,有助于写出可复用和可维护性高的程序。

1.程序设计的原则

单一职责原则(SRP)

一个对象或方法只做一件事情。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。

也就是说,应该把对象或方法划分成较小的粒度。

最少知识原则(LKP)

一个软件实体应当尽可能少地与其他实体发生相互作用。

应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系,可以转交给第三方进行处理。

划分模块的一个准则就是高内聚低耦合。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性(内聚性)越差( 降低耦合性,可以提高其独立性)。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。

开放-封闭原则(OCP)

软件实体(类、模块、函数)等应该是可以 扩展的,但是不可修改。

当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码,防止影响原系统的稳定。

2.常用模式

常用的有:单例模式、工厂模式、装饰模式、发布-订阅模式、观察者模式、中介者模式等。

3.单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。核心:确保只有一个实例,并提供全局访问。

实现方法:使用闭包缓存一个局部变量,这个局部变量用来缓存仅有的实例。

function SetManager(name) {
    this.manager = name;
}

SetManager.prototype.getName = function() {
    console.log(this.manager);
};

var SingletonSetManager = (function() {
    var manager = null;

    return function(name) {
        if (!manager) {
            manager = new SetManager(name);
        }

        return manager;
    } 
})();

SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a

4.工厂模式

工厂模式是用来创建对象的一种设计模式。不暴露对象创建的逻辑,而是将逻辑封装在一个函数内,那么这个函数可以成为工厂。

工厂是用构造函数的方法来创建对象。通过使用一个共同的接口来指向新创建的对象。

工厂模式根据抽象程度的不同可以分为:1.简单工厂 2.工厂方法 3.抽象工厂

简单工厂

优点:你只需要传递一个合法的参数,就可以获取到你想要的对象,而无需知道创建的具体的细节。

缺点:但是在函数内包含了所有对象的构造函数和判断逻辑的代码, 每次如果需要添加一个对象,那么我们需要新增一个构造函数,当我们需要维护的对象不是上面这2个,而是20个或者更多,那么这个函数将会成为超级函数,使得我们难以维护。所以简单工厂模式只适用于在创建时对象数量少,以及逻辑简单的情况。

let  factory = function (role) {
    function superman() {
        this.name ='超级管理员',
        this.role = ['修改密码', '发布消息', '查看主页']
    }

    function commonMan() {
        this.name = '普通游客',
        this.role = ['查看主页']
    }

    switch(role) {
        case 'superman':
        return new superman();
        break;
        case 'man':
        return new commonMan();
        break;
        default:
        throw new Error('参数错误')
    }
}

let superman = factory('superman');
let man = factory('man');

工厂方法:

工厂方法模式本意是将实际创造的对象推迟到子类中,这样核心类就变成了抽象类。

let factory = function (role) {
    if(this instanceof factory) {
        var s = new this[role]();
        return s;
    } else {
        return new factory(role);
    }
}

factory.prototype = {
    admin: function() {
        this.name = '平台用户';
        this.role = ['登录页', '主页']

    },
    common: function() {
        this.name = '游客';
        this.role = ['登录页']
    },
    test: function() {
        this.name = '测试';
        this.role =  ['登录页', '主页', '测试页'];
        this.test = '我还有一个测试属性哦'
    }
}

let admin = new factory('admin');
let common = new factory('common');
let test = new factory('test');

如果使用时忘记加new了, 那么我们就获取不到admin,common等对象了,这就比较安全了。

5.装饰模式

定义:以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

是一种“即用即付”的方式,能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

核心:是为对象动态加入行为,经过多重包装,可以形成一条装饰链

实现:最简单的装饰者,就是重写对象的属性。

var A = {
    score: 10
};

A.score = '分数:' + A.score;

可以使用传统面向对象的方法来实现装饰,添加技能。

function Person() {}

Person.prototype.skill = function() {
    console.log('数学');
};

// 装饰器,还会音乐
function MusicDecorator(person) {
    this.person = person;
}

MusicDecorator.prototype.skill = function() {
    this.person.skill();
    console.log('音乐');
};

// 装饰器,还会跑步
function RunDecorator(person) {
    this.person = person;
}

RunDecorator.prototype.skill = function() {
    this.person.skill();
    console.log('跑步');
};

var person = new Person();

// 装饰一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);

person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步

6.发布-订阅模式

定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

一个对象不用再显式地调用另外一个对象的某个接口而实现通知。

与传统的发布-订阅模式实现方式(将订阅者自身当成引用传入发布者)不同,在JS中通常使用注册回调函数的形式来订阅。

发布订阅是一种消息范式,消息的发送者(发布者)不会直接将消息传给特定的接收者(订阅者),而是将发布的消息分为不同的类别,无需了解有哪些订阅者。同样,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在,无需了解发布者发出了哪些消息。

优势:时间上的解耦,对象之间解耦。可以用在异步编程中与MVVM框架中。

缺点:1.创建订阅者本身要消耗一定的时间和内存,订阅的处理函数不一定会被执行,驻留内存有性能开销。2.弱化了对象之间的联系,复杂的情况下可能会导致程序难以跟踪维护和理解。

实现:JS中的事件就是经典的发布-订阅模式的实现

// 订阅1
document.body.addEventListener('click', function() {
    console.log('click1');
}, false);

// 订阅2
document.body.addEventListener('click', function() {
    console.log('click2');
}, false);

// 发布
document.body.click(); // click1  click2

小A在公司C完成了笔试及面试,小B也在公司C完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司C,导致公司C很不耐烦。

一种解决办法是 AB直接把联系方式留给C,有结果的话C自然会通知AB。这里的“询问”属于显示调用,“留给”属于订阅,“通知”属于发布。

// 观察者
var observer = {
    // 订阅集合
    subscribes: [],

    // 订阅
    subscribe: function(type, fn) {
        if (!this.subscribes[type]) {
            this.subscribes[type] = [];
        }
        
        // 收集订阅者的处理
        typeof fn === 'function' && this.subscribes[type].push(fn);
    },

    // 发布  可能会携带一些信息发布出去
    publish: function() {
        var type = [].shift.call(arguments),
            fns = this.subscribes[type];
        
        // 不存在的订阅类型,以及订阅时未传入处理回调的
        if (!fns || !fns.length) {
            return;
        }
        
        // 挨个处理调用
        for (var i = 0; i < fns.length; ++i) {
            fns[i].apply(this, arguments);
        }
    },
    
    // 删除订阅
    remove: function(type, fn) {
        // 删除全部
        if (typeof type === 'undefined') {
            this.subscribes = [];
            return;
        }

        var fns = this.subscribes[type];

        // 不存在的订阅类型,以及订阅时未传入处理回调的
        if (!fns || !fns.length) {
            return;
        }

        if (typeof fn === 'undefined') {
            fns.length = 0;
            return;
        }

        // 挨个处理删除
        for (var i = 0; i < fns.length; ++i) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    }
};

// 订阅岗位列表
function jobListForA(jobs) {
    console.log('A', jobs);
}

function jobListForB(jobs) {
    console.log('B', jobs);
}

// A订阅了笔试成绩
observer.subscribe('job', jobListForA);
// B订阅了笔试成绩
observer.subscribe('job', jobListForB);


// A订阅了笔试成绩
observer.subscribe('examinationA', function(score) {
    console.log(score);
});

// B订阅了笔试成绩
observer.subscribe('examinationB', function(score) {
    console.log(score);
});

// A订阅了面试结果
observer.subscribe('interviewA', function(result) {
    console.log(result);
});

observer.publish('examinationA', 100); // 100
observer.publish('examinationB', 80); // 80
observer.publish('interviewA', '备用'); // 备用

observer.publish('job', ['前端', '后端', '测试']); // 输出A和B的岗位


// B取消订阅了笔试成绩
observer.remove('examinationB');
// A都取消订阅了岗位
observer.remove('job', jobListForA);

observer.publish('examinationB', 80); // 没有可匹配的订阅,无输出
observer.publish('job', ['前端', '后端', '测试']); // 输出B的岗位

7.观察者模式

观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。

最大的区别是调度的地方。虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

// 观察者
class Observer {
    constructor() {
 
    }
    update(val) {
 
    }
}
// 观察者列表
class ObserverList {
    constructor() {
        this.observerList = []
    }
    add(observer) {
        return this.observerList.push(observer);
    }
    remove(observer) {
        this.observerList = this.observerList.filter(ob => ob !== observer);
    }
    count() {
        return this.observerList.length;
    }
    get(index) {
        return this.observerList(index);
    }
}
// 目标
class Subject {
    constructor() {
        this.observers = new ObserverList();
    }
    addObserver(observer) {
        this.observers.add(observer);
    }
    removeObserver(observer) {
        this.observers.remove(observer);
    }
    notify(...args) {
        let obCount = this.observers.count();
        for (let index = 0; index < obCount; index++) {
            this.observers.get(i).update(...args);
        }
    }
}

本文固定连接:https://code.zuifengyun.com/2020/06/1753.html,转载须征得作者授权。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持