目录
设计原则
- 开闭原则:告诉我们要【对扩展开放,对修改关闭】
- 里氏替换原则:告诉我们【不要破坏继承体系】
- 依赖倒置原则:告诉我们要【面向接口编程】
- 单一职责原则:告诉我们实现【类】要【职责单一】
- 接口隔离原则:告诉我们在设计【接口】的时候要【精简单一】
- 迪米特法则:告诉我们要【降低耦合度】
单例模式
这种设计模式的思想是确保一个类只有唯一实例,一般用于全局缓存,比如全局window,唯一登录浮窗等。采用闭包的方式实现如下
代码实现
在JavaScript里,实现单例的方式有很多种,其中最简单的一个方式是使用对象字面量的方法,其字面量里可以包含大量的属性和方法:
var mySingleton = {
property1: "something",
property2: "something else",
method1: function () {
console.log('hello world');
}
};
var single = (function () {
let instance;
function getInstance() {
// 如果该实例存在,则直接返回,否则就对其实例化
if (instance === undefined) {
instance = new Construct();
}
return instance
}
function Construct() {
// ... 生成单例的构造函数的代码
return{
name:"iwen",
getName(){
return this.name;
}
}
}
return {
getInstance: getInstance
}
})();
console.log(single.getInstance().getName());
<!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()
// 由于闭包,createLoginLayer对result的引用,所以当single.getSingle函数执行完之后,内存中并不会销毁result。
// 当第二次以后点击按钮,根据createLoginLayer函数的作用域链中已经包含了result,所以直接返回result
var createLoginLayer = single.getSingle(login.createLayout)
oBtn.onclick = function() {
var layout = createLoginLayer()
layout.style.display = 'block'
}
</script>
</html>
工厂模式
工厂模式介绍
工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,用工厂方法代替new操作的一种模式
代码实现
class Animal {
constructor(name) {
this.name = name
}
}
class Creator {
create(name) {
return new Animal(name)
}
}
var creator = new Creator()
var duck = creator.create('Duck')
console.log(duck.name) // Duck
var chicken = creator.create('Chicken')
console.log(chicken.name) // Chicken
let UserFactory = function (role) {
if (this instanceof UserFactory) {
var s = new this[role]();
return s;
} else {
return new UserFactory(role);
}
}
UserFactory.prototype = {
SuperAdmin: function () {
this.name = "超级管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理']
},
Admin: function () {
this.name = "管理员",
this.viewPage = ['首页', '通讯录', '发现页', '应用数据']
},
NormalUser: function () {
this.name = '普通用户',
this.viewPage = ['首页', '通讯录', '发现页']
}
}
let superAdmin = UserFactory('SuperAdmin');
let admin = UserFactory('Admin')
let normalUser = UserFactory('NormalUser')
原型模式
原型模式介绍
原型模式是指原型实例指向创建对象的种类,并通过拷贝这些原型创建新的对象,是一种用来创建对象的模式,也就是创建一个对象作为另一个对象的prototype属性
实现原型模式是在ECMAScript5中,提出的Object.create方法,使用现有的对象来提供新创建的对象的__proto__。
代码实现
<script>
var lynkCoPrototype = {
model: '领克',
getModel: function () {
console.log("车辆技术:" + this.model);
}
}
var volvo = Object.create(lynkCoPrototype, {
model: {
value: '沃尔沃'
}
})
volvo.getModel();//车辆技术:沃尔沃
</script>
适配器模式
适配器模式介绍
适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。别称包装器(wrapper)。
代码实现
class GooleMap {
show() {
console.log('渲染谷歌地图')
}
}
class BaiduMap {
show() {
console.log('渲染百度地图')
}
}
function render(map) {
if (map.show instanceof Function) {
map.show()
}
}
render(new GooleMap()) // 渲染谷歌地图
render(new BaiduMap()) // 渲染百度地图
但是假如BaiduMap类的原型方法不叫show,而是叫display,这时候就可以使用适配器模式了,因为我们不能轻易的改变第三方的内容。在BaiduMap的基础上封装一层,对外暴露show方法。
class GooleMap {
show() {
console.log('渲染谷歌地图')
}
}
class BaiduMap {
display() {
console.log('渲染百度地图')
}
}
// 定义适配器类, 对BaiduMap类进行封装
class BaiduMapAdapter {
show() {
var baiduMap = new BaiduMap()
return baiduMap.display()
}
}
function render(map) {
if (map.show instanceof Function) {
map.show()
}
}
render(new GooleMap()) // 渲染谷歌地图
render(new BaiduMapAdapter()) // 渲染百度地图
小结
- 适配器模式主要解决两个接口之间不匹配的问题,不会改变原有的接口,而是由一个对象对另一个对象的包装。
- 适配器模式符合开放封闭原则
代理模式
代理模式介绍
代理,顾名思义就是帮助别人做事,代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。
代理模式非常有意义,在生活中可以找到很多代理模式的场景,例如:明星都会有经纪人作为代理,如果想请明星做事情,要先找到他的经纪人,然后和经纪人把报酬和时间等细节都谈好了,再把商量好的合同给到明星签署。
代理模式多用于懒加载,合并请求和缓存等。
代码实现
class MyImage {
constructor() {
this.img = new Image()
document.body.appendChild(this.img)
}
setSrc(src) {
this.img.src = src
}
}
class ProxyImage {
constructor() {
this.proxyImage = new Image()
}
setSrc(src) {
let myImageObj = new MyImage()
myImageObj.img.src = 'file://xxx.png' //为本地图片url
this.proxyImage.src = src
this.proxyImage.onload = function() {
myImageObj.img.src = src
}
}
}
var proxyImage = new ProxyImage()
proxyImage.setSrc('http://xxx.png') //服务器资源url
本例中,本体类中有自己的setSrc方法,如果有一天网络速度已经不需要预加载了,我们可以直接使用本体对象的setSrc方法,,并且不需要改动本体类的代码,而且可以删除代理类。
// 依旧可以满足需求
var myImage = new MyImage()
myImage.setSrc('http://xxx.png')
小结
代理模式符合开放封闭原则 本体对象和代理对象拥有相同的方法,在用户看来并不知道请求的本体对象还是代理对象
策略模式
策略模式介绍
策略模式的本意将算法的使用与算法的实现分离开来,避免多重判断调用哪些算法。适用于有多个判断分支的场景
代码实现
// 对于vip客户
function vipPrice() {
this.discount = 0.5;
}
vipPrice.prototype.getPrice = function (price) {
return price * this.discount;
}
// 对于超级客户
function superVipPrice() {
this.discount = 0.3;
}
superVipPrice.prototype.getPrice = function (price) {
return price * this.discount;
}
// 对于普通客户
function Price() {
this.discount = 1;
}
Price.prototype.getPrice = function (price) {
return price;
}
// 上下文,对于客户端的使用
function Context() {
this.name = '';
this.strategy = null;
this.price = 0;
}
Context.prototype.set = function (name, strategy, price) {
this.name = name;
this.strategy = strategy;
this.price = price;
}
Context.prototype.getResult = function () {
console.log(this.name + ' 的结账价为: ' + this.strategy.getPrice(this.price));
}
var context = new Context();
var vip = new vipPrice();
context.set('vip客户', vip, 200);
context.getResult();
var old = new superVipPrice();
context.set('超级Vip客户', old, 200);
context.getResult();
var Price = new Price();
context.set('普通客户', Price, 200);
context.getResult();
迭代器模式
迭代器模式介绍
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
简单理解(白话理解):统一 “集合” 型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项中的数据结构)
生活小栗子:清单 TodoList。每日清单有学习类、生活类、工作类、运动类等项目,清单列表只管罗列,不管类别。
模式特点
- 为遍历不同数据结构的 “集合” 提供统一的接口;
- 能遍历访问 “集合” 数据中的项,不关心项的数据结构
代码实现
var each = function (arr, callBack) {
for (let i = 0, len = arr.length; i < len; i++) {
// 将值,索引返回给回调函数callBack处理
if (callBack(i, arr[i]) === false) {
break; // 中止迭代器,跳出循环
}
}
}
// 外部调用
each([1, 2, 3, 4, 5], function (index, value) {
console.log(index, value);
})
观察者模式
观察者模式介绍
也叫发布订阅模式,在这种模式中,一个订阅者订阅发布者,当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者。
使用观察者模式的好处:
- 支持简单的广播通信,自动通知所有已经订阅过的对象。
- 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
- 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。
发布订阅模式的构成
最常见的发布订阅模式就是咱们DOM事件,仔细回想一下我们要给一个按钮,绑定一个事件,当我点击按钮的时候我要让他的颜色变了,并且页面弹出一个弹出框
我们分析一下这个流程
首先,我们得知道给哪个按钮的时候绑定事件,然后我们得知道触发事件以后需要干什么?
那么在这其中谁是发布者?
是DOM中的按钮,因为是在它身上绑定了事件,当我们点击按钮的时候它便向订阅者发布了这个消息
那么谁是订阅者?
是click事件,当点击按钮时,dom发布了一条消息,而事件订阅了它,所以当它被点击的时候,订阅者会接收到消息
代码实现
class Event {
constructor() { }
// 首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)
handlers = {}
// 事件添加方法,参数有事件名和事件方法
addEventListener(type, handler) {
// 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器
if (!(type in this.handlers)) {
this.handlers[type] = []
}
// 将事件存入
this.handlers[type].push(handler)
}
// 触发事件两个参数(事件名,参数)
dispatchEvent(type, ...params) {
// 若没有注册该事件则抛出错误
if (!(type in this.handlers)) {
return new Error('未注册该事件')
}
// 便利触发
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)
removeEventListener(type, handler) {
// 无效事件抛出
if (!(type in this.handlers)) {
return new Error('无效事件')
}
if (!handler) {
// 直接移除事件
delete this.handlers[type]
} else {
const idx = this.handlers[type].findIndex(ele => ele === handler)
// 抛出异常事件
if (idx === undefined) {
return new Error('无该绑定事件')
}
// 移除事件
this.handlers[type].splice(idx, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
var event = new Event() // 创建event实例
// 定义一个自定义事件:"load"
function load(params) {
console.log('load', params)
}
event.addEventListener('load', load)
// 再定义一个load事件
function load2(params) {
console.log('load2', params)
}
event.addEventListener('load', load2)
// 触发该事件
event.dispatchEvent('load', 'load事件触发')
// 移除load2事件
event.removeEventListener('load', load2)
// 移除所有load事件
event.removeEventListener('load')
命令模式
命令模式介绍
命令模式(Command)的定义是:用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。
代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cmd-demo</title>
</head>
<body>
<div>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
</div>
<script>
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
// 定义一个命令发布者(执行者)的类
class Executor {
setCommand(btn, command) {
btn.onclick = function() {
command.execute()
}
}
}
// 定义一个命令接收者
class Menu {
refresh() {
console.log('刷新菜单')
}
addSubMenu() {
console.log('增加子菜单')
}
}
// 定义一个刷新菜单的命令对象的类
class RefreshMenu {
constructor(receiver) {
// 命令对象与接收者关联
this.receiver = receiver
}
// 暴露出统一的接口给命令发布者Executor
execute() {
this.receiver.refresh()
}
}
// 定义一个增加子菜单的命令对象的类
class AddSubMenu {
constructor(receiver) {
// 命令对象与接收者关联
this.receiver = receiver
}
// 暴露出统一的接口给命令发布者Executor
execute() {
this.receiver.addSubMenu()
}
}
var menu = new Menu()
var executor = new Executor()
var refreshMenu = new RefreshMenu(menu)
// 给按钮1添加刷新功能
executor.setCommand(btn1, refreshMenu)
var addSubMenu = new AddSubMenu(menu)
// 给按钮2添加增加子菜单功能
executor.setCommand(btn2, addSubMenu)
</script>
</body>
</html>
状态模式
状态模式介绍
状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。
代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>state-demo</title>
</head>
<body>
<button id="btn">开关</button>
<script>
// 定义一个关闭状态的类
class OffLightState {
constructor(light) {
this.light = light
}
// 每个类都需要这个方法,在不同状态下按都需要触发这个方法
pressBtn() {
this.light.setState(this.light.weekLightState)
console.log('开启弱光')
}
}
// 定义一个弱光状态的类
class WeekLightState {
constructor(light) {
this.light = light
}
pressBtn() {
this.light.setState(this.light.strongLightState)
console.log('开启强光')
}
}
// 定义一个强光状态的类
class StrongLightState {
constructor(light) {
this.light = light
}
pressBtn() {
this.light.setState(this.light.offLightState)
console.log('关闭电灯')
}
}
class Light {
constructor() {
this.offLightState = new OffLightState(this)
this.weekLightState = new WeekLightState(this)
this.strongLightState = new StrongLightState(this)
this.currentState = null
}
setState(newState) {
this.currentState = newState
}
init() {
this.currentState = this.offLightState
}
}
let light = new Light()
light.init()
var btn = document.getElementById('btn')
btn.onclick = function() {
light.currentState.pressBtn()
}
</script>
</body>
</html>
小结
- 通过定义不同的状态类,根据状态的改变而改变对象的行为,
- 不必把大量的逻辑都写在被操作对象的类中,而且容易增加新的状态
- 符合开放封闭原则