设计模式: 软件设计中常见问题的解决方案模型 目前设计模式有23种,主要分为三类:
- 创建型: 如何创造对象。
- 结构型: 如何灵活地将对象组装成较大的结构。
- 行为型: 负责对象间的高效通信和职责划分。
接下来,本文会介绍几个常用的设计模式。
单例模式
定义: 一个类能返回一个对象的引用(并且永远是同一个)。
应用场景:全局唯一访问对象,如缓存、全局状态管理、浏览器中的windows对象。
实例:假设存在一个myClass类(由于JS默认没有类的概念,因此使用对象表示),这个类中有个对象person,和一个用于设置名字的setName方法,这个方法可以对person的name属性赋值。
let myClass = {
person: {},
setName: function (name) {
this.person.name = name
return this.person
}
}
console.log(myClass.setName('a') === myClass.setName('b'))
可以看到,控制台的返回结果为true,说明这两个函数调用的返回结果是一样的,说明它们指向的都是同一个对象。
原型模式
定义: 复制已有的对象以创建新的对象。
应用场景: 用同一个原型new出来的实例,拥有相同的原型上的属性和方法。
实例:
function Person() {
}
Person.prototype.name = 'song'
let person1 = new Person()
let person2 = new Person()
console.log(person1.name)
console.log(person1.name === person2.name)
结果分别是song和true说明创建的不同对象,拥有相同的属性。
工厂模式
我们可以使用Object构造函数来创建单个对象。但是,使用同一个接口创建很多对象时,会产生大量重复的代码。为了解决这个问题,我们可以使用工厂模式。 工厂模式分为以下三种。
简单工厂模式
简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
function Dog(name, color){
let dog = {}
dog.name = name;
dog.color = color;
return dog
}
const dog = Dog('stack', 'black')
这样我们就可以使用Dog方法批量创建狗狗对象。
工厂方法模式
工厂方法模式定义创建对象的接口,但是让子类决定实例化哪个类。工厂方法将类的实例化延迟到子类。
function Cat(name) {
this.name = name
}
function Dog(name) {
this.name = name
}
function Animal(type, name) {
// 让子类的type参数觉醒实例化哪个类
this.instance = this[type](name)
return this.instance
}
// 父类的代码,判断type,并执行类的实例化
Animal.prototype = {
cat: function(name){
let cat = new Cat(name)
cat.food = 'fish'
return cat
},
dog: function(name){
let dog = new Dog(name)
dog.food = 'meet'
return dog
}
}
const dog = new Animal('dog', '狗子')
console.log(`the dog's name is ${dog.name} and his food is ${dog.food}`)
const cat = new Animal('cat', '猫咪')
console.log(`the cat's name is ${cat.name} and his food is ${cat.food}`)
我们在简单工厂模式的基础上,封装了一个父类Animal,可以通过给父类传递参数type,让父类决定实例化某个类。上述代码的执行结果如下:
the dog's name is 狗子 and his food is meet
the cat's name is 猫咪 and his food is fish
抽象工厂模式
抽象工厂模式是对类的工厂的抽象,用于创建产品类,而不是创建产品类的实例。 这一部分使用相对较少,故不再展开讨论。
适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。比如:手机不能插在电脑上,必须有一个转换器,这个转换器就是适配器。
适配器模式也经常用于日常的数据处理上,比如把一个对象转化成我们需要的数组格式:
const infos = {
'周一': 600,
'周二': 200
}
const adapter = origin_data => {
let date = []
let num = []
for (const key in origin_data) {
date.push(key)
num.push(origin_data[key])
}
return [date, num]
}
console.log(adapter(infos))
当然,适配器函数的内容是根据需要自定义的。
代理模式
代理模式可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
const person = {
name: 'moon song'
}
function proxy(key){
if (key === name) return obj[key]
else return new Error('对象中没有对应的key')
}
proxy(person, 'name')
其实上述proxy就是一个很简单的代理,当想要获取某个对象的某个属性时进行判断,存在则返回,不存在则报错。
发布订阅模式
希望接收通知的对象 follower 通过自定义事件订阅消息,被激活事件的对象 Book 通过发布主题事件的方式通知各个订阅该主题的 follower 对象。
实例:以下代码实现了一个简单的书籍订阅功能。
function Book() {
const bookList = []
return {
// 订阅函数
subscribe: (people) => {
if (bookList.indexOf(people) === -1) {
bookList.push(people)
console.log(`${people.name}订阅了`)
}
},
// 取消订阅函数
unsubscribe: (people) => {
const index = bookList.indexOf(people)
if (index === -1) return
bookList.splice(index, 1)
console.log(`${people.name}取消订阅了`)
},
// 发布数据
notify: () => {
// 调用订阅者的通知函数
bookList.forEach(people => people.update('update'))
}
}
}
function follower(name) {
this.name = name
// 订阅者自定义的通知函数
this.update = (data) => {
console.log(`${name}: 得到了通知${data}`)
}
}
// 实例化对象和两个订阅者
const book = new Book()
const p1 = new follower('张三')
const p2 = new follower('李四')
// 测试代码
book.subscribe(p1)
book.subscribe(p2)
book.unsubscribe(p2)
book.notify()
book.subscribe(p2)
book.notify()