设计模式
简单了解下什么是设计模式,具体用来干啥的,怎么用,再举举举举栗子…
一、是什么
设计模式是什么?
设计模式是一种解决方案,是一套总结,是软件开发人员在软件开发过程中对一般问题的解决方案。
二、做什么
1.都是前辈们开发经验的积累和总结,归纳为设计模式,用来提供一些最佳实践;如在开发或者设计系统时,根据问题类型参考现有设计模式,可以借用前人经验,巧妙高效地解决问题;
2.它提供了一套大家都懂的“词汇”(机制),方便开发者之间更容易沟通,节约沟通成本;
3.可以使人们更加深入了解面向对象的设计思想,且对软件设计的思想/结构/代码等,都有更高的复用性,可提高软件开发效率,节约成本。
三、概念及举栗
1.总体性概念及分类
总共23中设计模式,分三类:创建型模式、结构型模式、行为型模式
(还有另外的设计模式,如:J2EE设计模式)
创建型模式:提供创建对象的方法,但不是直接使用new来创建对象,而是隐藏了创建的逻辑,使得程序在创建对象时更加灵活(什么时候创建,以及怎么创建)
结构型模式:关注类和对象的组合,用继承、接口和定义对象等方法来定义新的类和对象,实现一些新的功能。
行为型设计模式:关注的是对象之间的通信,以及一些对象的改变对另外一些对象的影响。
具体分类:
- 创建型模式
工厂模式、抽象工厂、单例、建造者、原型模式 - 结构型模式
适配器模式、桥接、过滤器、组合、装饰器、外观、享元、代理模式 - 行为型模式
责任链模式、命令、解释器、迭代器、中介者、备忘录、观察者、状态、空对象、策略、模板、访问者模式
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 平方米
学习设计模式可以方便开发者之间的沟通交流/代码阅读,并对一类问题可以快速定位,巧妙高效地解决问题。