软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。**
设计模式的分类
设计模式可以分为三大类,分别是创建型设计模式、行为型设计模式以及结构型设计模式。
创建型的设计模式:
- 单例模式(Singleton)
- 构建模式(Builder)
- 原型模式(Prototype)
- 抽象工厂模式(Abstract Factory)
- 工厂方法模式(Factory Method)
行为设计模式:
- 策略模式(Strategy)
- 状态模式(State)
- 责任链模式(Chain of Responsibility)
- 解释器模式(Interpreter)
- 命令模式(Command)
- 观察者模式(Observer)
- 备忘录模式(Memento)
- 迭代器模式(Iterator)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
结构型设计模式:
- 装饰者模式(Decorator)
- 代理模式(Proxy)
- 组合模式(Composite)
- 桥连接模式(Bridge)
- 适配器模式(Adapter)
- 蝇量模式(Flyweight)
- 外观模式(Facade)
1.单例模式
一个类只有唯一的一个实例,这个实例在整个程序中有一个全局的访问点。
翻译过来想表达的就是,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回。是我们平常中用的最多的情况。
<!DOCTYPE html>
<html lang="en">
<body>
<button id="btn">登录</button>
</body>
<script>
class Login {
createLayout() {
var oDiv = document.createElement('div')
oDiv.innerHTML = '我是登录框'
document.body.appendChild(oDiv)
oDiv.style.display = 'none'
return oDiv
}
}
class Single {
getSingle(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments))
}
}
}
var oBtn = document.getElementById('btn')
var single = new Single()
var login = new Login()
var createLoginLayer = single.getSingle(login.createLayout)
oBtn.onclick = function() {
var layout = createLoginLayer()
layout.style.display = 'block'
}
</script>
</html>
2.工厂模式
工厂模式属于创建型设计模式,其主要作用就是实现通过 一个函数 可以 创建不同类型的实例对象
简单使用
// 工厂函数
function createPerson(name, age) {
let person = {};
person.name = name;
person.getName = function() {
return this.name;
}
return person;
}
// 通过工厂函数创建 person 实例
let person1 = createPerson('momo', 18);
console.log(person1.getName());
综合使用
// Person 构造函数
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
// Car 构造函数
function Car(model) {
this.model = model;
}
Car.prototype.getModel = function () {
return this.module;
}
// 工厂函数
function create(type, param) {
return new this[type](param);
}
// 绑定对应的构造函数到 create 原型上
create.prototype = {
person: Person,
car: Car
}
// 通过工厂函数创建 Person 实例
let person1 = new create('person', 'momo');
console.log(person1.getName());
// 通过工厂函数创建 Car 实例
let car1 = new create('car', '奥迪');
console.log(car1.getModel());
说明:
- 通过 new 调用 create 函数时,底层会创建一个新的对象,该对象的
__proto__
等于create.prototype
- 并且 create 函数中的 this 也指向了底层创建的这个新的对象
- 当执行到
new this[type](param)
语句时
(1) 先使用 this[type]
通过底层创建的实例取出在原型链上绑定的 type 类型的构造函数
(2) new this[type](param)
传入 param
参数通过 new
调用该构造函数
(3) 返回 type 类型的实例对象 **
3、构建模式
构建模式也属于创建型设计模式,使用建造模式来创建对象 更注重创建对象的细节 ,这种模式创建出的复杂对象或者复合对象 结构会非常清晰 。
下面给出一个需求场景:
我们通过 ajax 请求获取到了服务端返回的一组简历数据,为了做成统一的电子版简历,我们需要将这些信息进行整理,我们该如何去做呢?
下面使用 构造模式 来处理这些数据
// 将传来的 param 数据抽象为三个类
function formatData(param) {
let _candidate = new Person(param);
_candidate.name = new CreateName(param.name);
_candidate.work = new CreateWork(param.work);
return _candidate;
}
// 然后把对于具体数据处理的逻辑代码封装在对应的构造函数中
function Person(param) {
this.name = param.name;
this.age = param.age;
}
function CreateName(name) {
this.name = name;
this.lastName = name.split(' ')[0];
this.firstName = name.split(' ')[1];
}
function CreateWork(work) {
switch (work) {
case 'engineer':
this.name = '工程师';
this.description = '热爱编程';
break;
case 'teacher':
this.name = '老师';
this.description = '教书育人';
break;
default:
this.name = work;
this.description = '无';
}
}
CreateWork.prototype.changeWork = function (work) {
this.name = work;
}
CreateWork.prototype.changeDes = function (des) {
this.description = des;
}
// 使用 formatData 处理数据
let candidateArr = [];
for (let i = 0; i < data.length; i++) {
// 将整理的数据保存到数组中
candidateArr.push(formatData(data[i]));
}
console.log(candidateArr);
4、组合模式
组合模式作用于将多个部分通过组合变成一个整体。
场景:
比如我们去麦当劳店了一个汉堡、两份薯条和一杯可乐,我们可以把这些东西看成一个个组件,通过组合就可以将整个套餐产出给顾客。
const MacroCommand = function () {
return {
lists: [],
add: function (task) {
this.lists.push(task)
},
excute: function () {
for (let i = 0; i < this.lists.length; i++) {
this.lists[i].excute()
}
},
}
}
// 套餐1
const command1 = MacroCommand()
command1.add({
excute: () => console.log('薯条')
})
command1.add({
excute: () => console.log('麦旋风')
})
// 套餐2
const command2 = MacroCommand() // 组合对象
command2.add({
excute: () => console.log('汉堡')
})
command2.add({
excute: () => console.log('可乐')
})
// 套餐3
const command3 = MacroCommand()
command3.add({
excute: () => console.log('甜筒')
})
command3.add({
excute: () => console.log('鸡块')
})
// 同时买下套餐1、套餐2、套餐3
const macroCommand = MacroCommand()
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.add(command3)
macroCommand.excute()
5、策略模式
定义: 根据不同参数可以命中不同的策略
策略模式将不同算法进行合理的分类和单独封装,让不同算法之间可以互相替换而不会影响到算法的使用者
- 实现不同, 作用一致
- 调用方式相同,降低了使用成本以及不同算法之间的耦合
- 单独定义算法模型, 方便单元测试
- 避免大量冗余的代码判断,比如if else等
根据不同的参数(level)获得不同策略方法(规则),
const strategy = {
'S': function(salary) {
return salary * 4
},
'A': function(salary) {
return salary * 3
},
'B': function(salary) {
return salary * 2
}
}
const calculateBonus = function(level, salary) {
return strategy[level](salary)
}
calculateBonus('A', 10000) // 30000
策略模式的使用常常隐藏在高阶函数中, 稍微变换下上述 demo 的形式如下, 可以发现我们平时已经在使用它了。
const S = function(salary) {
return salary * 4
}
const A = function(salary) {
return salary * 3
}
const B = function(salary) {
return salary * 2
}
const calculateBonus = function(func, salary) {
return func(salary)
}
calculateBonus(A, 10000) // 30000
6、命令模式
命令模式与策略模式有些类似, 在 JavaScript 中它们都是隐式的。
下面代码中对按钮和命令进行了抽离, 因此可以复杂项目中可以使用命令模式将界面的代码和功能的代码交付给不同的人去写。
const setCommand = function(button, command) {
button.onClick = function() {
command.excute()
}
}
// -------------------- 上面的界面逻辑由A完成, 下面的由B完成
const menu = {
updateMenu: function() {
console.log('更新菜单')
},
}
const UpdateCommand = function(receive) {
return {
excute: receive.updateMenu,
}
}
const updateCommand = UpdateCommand(menu) // 创建命令
const button1 = document.getElementById('button1')
setCommand(button1, updateCommand)
通过将数据和事件接口化来构建若干个子类。
例子;得到不同生产商牛奶的名字;使用共同的接口,避免每次创建新对象都会new
<script>
function Milke(name){
let m={};
m.name=name;
m.getname=function(){
console.log(this.name)
}
return m;
}
let ty= Milke( 'ty')
ty.getname();
console.log(ty.name);
</script>
类的写法:使用共同的接口,避免每次创建新对象都会new
7 原型模式(Prototype)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
与原型继承是同一个意思。
在JavaScript中,实现原型模式是在ECMAScript5中,提出的Object.create方法,使用现有的对象来提供新创建的对象的proto。
<script>
var a= {
name: 'everyone',
getName: function() {
return this.name
}
}
var b = Object.create(a, {
classroom: {
value: '504'
}
})
console.log(b.getName()) // everyone
console.log(b.classroom) // 504
console.log(b.__proto__ === a) //true
现在es6有了类的思想后,对于其的实现更加简单。
例子,孩子继承父母的房产
<script>
class Parent{
constructor(){
}
home(){
console.log('apartment');
}
}
//继承
class Child extends Parent{
}
let son= new Child();
son.home();
8.迭代器模式
在不暴露对象内部结构的同时,可以顺序地访问集合对象内部的各个元素。
9.状态模式
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为。
我们说到封装一开始想到是封装函数,和对象。但是在状态模式下,我们封装的是对象的状态。
例子:开关灯
<script>
var d = {
off: {
// 状态对应的行为
done: function () {
console.log('关灯');
this.button.innerHTML = '关';
// 下一个状态
this.next =d.on;
}
},
on: {
done: function () {
console.log('开灯');
this.button.innerHTML = '开';
this.next = d.off;
}
}
}
var Light = function () {
// 设置初始状态
this.next = d.off;
this.button = null;
}
Light.prototype.init = function () {
var button = document.createElement('button');
var self = this;
button.innerHTML = '开灯'
this.button = document.body.appendChild(button);
this.button.onclick = function () {
self.next.done.call(self);
}
}
var light = new Light();
light.init();
</script>
10.观察者模式
观察者模式又叫做发布-订阅模式。这是一种一对多的对象依赖关系,当被依赖的对象的状态发生改变时,所有依赖于它的对象都将得到通知。
//观察者模式的模板
var observer = (function() {
var orderList = {},
listen,
publish,
remove;
listen = function(id, fn) {
...
};
publish = function() {
...
};
remove = function(id, fn) {
...
};
return {
listen: listen,
publish: publish,
remove: remove
}
})();
11.装饰器模式
在不改变原对象的基础上,通过对其进行包装拓展,使得原有对象可以动态具有更多功能,从而满足用户的更复杂需求”。
例子;普通车10块,新车11块
<script>
function car(){
this.price=10;
}
function newcar(carClass){
carClass.isnew=true;
carClass.price+=1;
}
var car = new car();
console.log( car.price);
newcar(car);
console.log(car.price);
12.代理模式
当多个对象需要处理同一个请求的时候,可以把这些请求交给另外一个对象处理
委托模式写法;把子元素的事件委托给父元素去绑定执行,这样我们为父元素绑定一个事件,通过委托模式就实现了所有子元素的点击事件需求
<body>
<ul id='up'>
<li>a </li>
<li>b </li>
<li>c </li>
</ul>
</body>
<script>
var ul=document.getElementById('up');
ul.onclick=function(e){
var e=e||window.event,
tar=e.target||e.srcElement;
if(tar.nodeName.toLowerCase()==='li'){
tar.style.backgroundColor='yellow';
}
}
</script>