设计模式的学习

设计模式

简单了解下什么是设计模式,具体用来干啥的,怎么用,再举举举举栗子…

一、是什么

设计模式是什么?
设计模式是一种解决方案,是一套总结,是软件开发人员在软件开发过程中对一般问题的解决方案。

二、做什么

1.都是前辈们开发经验的积累和总结,归纳为设计模式,用来提供一些最佳实践;如在开发或者设计系统时,根据问题类型参考现有设计模式,可以借用前人经验,巧妙高效地解决问题;
2.它提供了一套大家都懂的“词汇”(机制),方便开发者之间更容易沟通,节约沟通成本;
3.可以使人们更加深入了解面向对象的设计思想,且对软件设计的思想/结构/代码等,都有更高的复用性,可提高软件开发效率,节约成本。

三、概念及举栗

1.总体性概念及分类

总共23中设计模式,分三类:创建型模式、结构型模式、行为型模式
(还有另外的设计模式,如:J2EE设计模式)

创建型模式:提供创建对象的方法,但不是直接使用new来创建对象,而是隐藏了创建的逻辑,使得程序在创建对象时更加灵活(什么时候创建,以及怎么创建)
结构型模式:关注类和对象的组合,用继承、接口和定义对象等方法来定义新的类和对象,实现一些新的功能。
行为型设计模式:关注的是对象之间的通信,以及一些对象的改变对另外一些对象的影响。

具体分类:

  1. 创建型模式
    工厂模式、抽象工厂、单例、建造者、原型模式
  2. 结构型模式
    适配器模式、桥接、过滤器、组合、装饰器、外观、享元、代理模式
  3. 行为型模式
    责任链模式、命令、解释器、迭代器、中介者、备忘录、观察者、状态、空对象、策略、模板、访问者模式

tips:J2EE模式:MVC模式、业务代表、组合实体、数据访问对象模式…

2.栗子

在不同的编程语言中,对设计模式的实现可能是有区别的,但是对于问题的解决思路是一致的。那对于我们前端开发者来说,怎么运用它呢?下面举了几个栗子,Let’s take a look~

(1)单例模式

定义:是保证一个类只有一个实例,并且提供一个访问它的全局访问点,来供外部访问。
关键点:它的构造函数是私有的。
主要解决概况:避免一个全局使用的类被频繁的创建和销毁,以节省系统资源。
缺点:没有接口,不能继承。
优点:避免对资源的多重占用,减少内存开销。
应用实例:
Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
总结:在一些对象上往往只需要一个,比如浏览器window对象,登录窗,全局缓存,线程池等,在进行相应的实现时,可用一个变量标识某个类是否已经创建过对象,若是,则在获取这个类实例时,直接返回创建过的对象;若否,则创建一个唯一的实例。
举栗:
<1> 基础栗子

var SingleMode = function(name, instance = { age: '18'}){
    this.name = name;
    this.instance = instance;
};
SingleMode.prototype.getName = function(){
    return this.name;
};
SingleMode.getInstance = function(name) {
    if(!this.instance) {
        this.instance = new SingleMode(name, { age: '19' });
    }
    return this.instance;
};
// 创建多个实例打印结果
var x = SingleMode.getInstance("xxx");
var y = SingleMode.getInstance("yyy");
var z = SingleMode.getInstance("zzz");

// 不管创建几个实例,最后都以第一个创建成功的为准
console.log('x:', x); // {name: "xxx", instance: { age: "19" }}
console.log('y:', y); // {name: "xxx ", instance: { age: "19" }}
console.log('z:', z); // {name: "xxx ", instance: { age: "19" }}
console.log(x===y); // true
console.log(x===y && y === z); // true

<2>实践栗子

// 立即执行函数
(function () {
    // 若实例存在,则直接返回;否则直接实例化 
   var getSingleInstance = function(fn){ 
       // 参数为创建对象的方法
       var result;
       return function(){
           return result || (result = fn.apply(this,arguments));
       };
   };
    // 创建登录窗口
    var createLoginElement = function(){
        var divElement = document.createElement('div');
        divElement.innerHTML = '登录窗口';
        divElement.style.display = 'none';
        document.body.appendChild(divElement);
        return divElement;
    };
    // 单例方法
    var createSingleLogin = getSingleInstance(createLoginElement);
    // 点击创建
    document.getElementById('loginBtn').onclick = function(){
        var loginBtn = createSingleLogin();
        loginBtn.style.display = 'block';
    };
})()
(2)策略模式

定义:定义一系列算法,并将每个算法封装起来,它们可以相互替换,并且算法的改变不会影响用户的使用。
关键点:实现同一个接口。
主要解决概况:在多种算法相似的情况下,使用if…else难以维护;可以将这些算法封装册成一个个类,任意的替换。
缺点:所有策略类都需要对外暴露。
优点:算法可以自由切换并且避免了使用多重条件判断,扩展性良好。
应用实例:旅行的出行方式有:汽车、高铁、飞机,每一种出行方式都是一种策略。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
总结:
1.将变化的算法封装成独立的策略函数,并负责具体的计算;
2.客户端必须知道所有的策略类并自行决定使用哪一个策略类,意味着客户端必须理解这些算法的区别,以选择适合的一类;
3.委托函数,该函数接受客户请求,并将请求委托给某一个具体的策略函数来实现相应请求。
<1>最初实现

// 某文具店大促销,满100减20;满300减70;满500减120
// 按照促销类型,可定义枚举值0,1,2

var getPay = function (proEnum, actualMoney) {
    if (proEnum === 0) {
        return actualMoney - Math.floor(actualMoney / 100) * 20;
    }
    if (proEnum === 1) {
        return actualMoney - Math.floor(actualMoney / 300) * 70;
    }
    if (proEnum === 0) {
        return actualMoney - Math.floor(actualMoney / 500) * 120;
    }
}
console.log(getPay(0, 3456.00)); //2776

<2>当促销类型更多时,可将每个算法都封装成一个独立的函数

var promotion0 = function (actualMoney) {
    return actualMoney - Math.floor(actualMoney / 100) * 20;
}
var promotion1 = function (actualMoney) {
    return actualMoney - Math.floor(actualMoney / 300) * 70;
}
// ...
var promotionn = function () {
    // ...
}

var getPay = function (proEnum, actualMoney) {
    switch (proEnum) {
        case 0:
            return promotion0(actualMoney);
        case 1:
            return promotion1(actualMoney);
            // ...
            // case n:
            //   return ...
        default:
            return actualMoney;
    }
}
console.log(getPay(0, 3456.00)); //2776

<3>运用策略模式,把算法封装,并且可替换,将计算全权委托给了策略函数,不影响用户使用

var policies = {
    "Type_0": function (actualMoney) {
        return actualMoney - Math.floor(actualMoney / 100) * 20
    },
    "Type_1": function (actualMoney) {
        return actualMoney - Math.floor(actualMoney / 300) * 70
    },
    // ...
    "Type_n": function (actualMoney) {
        // ... 
    }
}

var getPay = function (proEnum, actualMoney) {
    return policies[`Type_${proEnum}`](actualMoney)
}
console.log(getPay(0, 3456.00)); //2776
(3)简单工厂模式

定义:定义一个用于创建产品/对象的工厂接口,将产品对象的实际创建工作放到具体的子工厂类中去实现,也就是提供创建一个实例的功能而不用考虑如何去实现它。
关键点:一个产品的创建过程是在子工厂类中完成的。
主要解决概况:主要解决接口选择的问题。
缺点:每增加一个新的产品/对象,就要相应的增加一个新的具体工厂类,在函数实现时会很复杂,也增加系统的复杂度,难以维护。
优点:(1)用户只需要知道工厂名称/参数就可以得到具体的产品/对象,而不用去管产品/对象的具体创建细节;
(2)在增加新的产品时,只需要增加一个具体的产品类和具体工厂类,而无需对原工厂进行修改,符合开闭原则
应用实例:当需要一台机器时,可以直接从工厂中拿货,而不用去管这台机器是怎样做出来的。
总结:简单工厂模式只适用于创建的对象数量较少,对象的创建逻辑不复杂时使用。
<1>栗子

// 一个系统分不同权限级别查看不同模块,可以在不同用户的构造函数设置
// 用户权限-UserRights简单工厂,构造对应不同权限用户函数
let UserRights = function (role) {
    function SuperAdmin() {
        this.name = "超管",
            this.viewModule = ['首页', '电子耳标管理', '档案管理', '动物检疫', '屠宰场管理']
    }
    function Admin() {
        this.name = "管理员",
            this.viewModule = ['首页', '电子耳标管理', '档案管理',]
    }
    function NormalUser() {
        this.name = '普通用户',
            this.viewModule = ['首页', '动物检疫']
    }

    switch (role) {
        case 'superAdmin':
            return new SuperAdmin();
            break;
        case 'admin':
            return new Admin();
            break;
        case 'user':
            return new NormalUser();
            break;
        default:
            break;
    }
}
let user1 = UserRights('superAdmin');
let user2 = UserRights('admin');
let user3 = UserRights('user'); 
(4)代理模式

定义:为某对象提供代理以限制对该对象的访问(即客户端通过代理间接访问对象,从而限制、修改对该对象的一些特性)
关键点:
主要解决概况:增加中间层,实现与被代理类的组合,避免直接访问对象所带来的问题。
缺点:在相关访问与实际对象之间增加了代理对象,可能会造成请求的处理速度变慢;需要处理代理对象的实现。
优点:智能化;职责清晰。
应用实例:买票,不一定去车站买,也可以在代售点买;支票呀代替money。
注意事项:
<1>栗子

(function () {
    // 目标对象
    function Subject() { }
    Subject.prototype.request = function () { };
    // 代理对象,控制对目标对象的访问
    function Proxy(realSubject) {
        this.realSubject = readSubject;
    }
    Proxy.prototype.request = function () {
        this.realSubject.request();
    };
}());
(5)观察者模式

定义:对象间的一种一对多的依赖关系,当一个状态改变时,所有它的依赖者都会收到通知并自动更新;又叫发布-订阅模式,定义对象之间一对多的依赖关系,
关键点:对一个对象更新,其他对象也需要同步更新(动态可变)。
总结:对象只需要将自己的更新通知其他对象,而不用知道其他对象的细节问题。

举栗:
<1>基础栗子,给一个dom节点绑定一个点击事件

document.body.addEventListener("click", function() { 
 alert("Hello World"); //  body节点发布消息
},false ) 
document.body.click() // 订阅点击事件

<2>实践栗子

// 售楼处
var buildSales = {};
// 缓存列表,存放订阅者的回调函数             
buildSales.clientList = [];
// 增加订阅者           
buildSales.listen = function (fn) {     
    // 订阅的消息添加进缓存列表       
    this.clientList.push(fn);                  
};
// 发布消息
buildSales.trigger = function () {               
    for (var i = 0, fn; fn = this.clientList[i++];) {
        fn.apply(this, arguments);             // arguments 是发布消息时带上的参数
    }
};
//调用
//订阅消息
buildSales.listen(function (price, squareMeter) {
    console.log('价格= ' + price);
    console.log('squareMeter= ' + squareMeter);
});
buildSales.trigger(2000000, 88);                // 输出:200 万,88 平方米

学习设计模式可以方便开发者之间的沟通交流/代码阅读,并对一类问题可以快速定位,巧妙高效地解决问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值