大家好,这里是大家的林语冰。欢迎关注"前端俱乐部"~
地球人都知道,JS 是一门混合范型语言,所以JS 也支持面向对象编程,其中设计模式则号称后 ES6 时代 JS 或 TS 面向对象编程的最佳实践。
"设计模式 "源于 GOF(四人帮)合著出版的一部经典作品《设计模式:可复用的面向对象软件元素》,该书第一次完整科普了软件开发中设计模式的概念。
设计模式一般分为三大类:
- 创建型模式
- 结构型模式
- 行为型模式
本期共享的是,JS 中面向对象编程和设计模式的集大成者:创建型模式和结构型模式。
免责声明
本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考,英文原味版请传送 Quick Reference Guide to Design Patterns in JS。
创建型模式
创建型模式(Creational)是一种设计模式类别,用于解决与对象创建情况相关的常见问题。
单例模式
单例模式(Singleton)限制特定对象的实例数量有且仅有 1
个。单例模式减少了全局变量的需求,规避了命名冲突的风险。
举个栗子,我们检查 constructor()
中是否已存在一个异性伴侣实例,或者是否需要新建一个新实例。
js
// 异性伴侣
class Soulmate {
constructor() {
if (typeof Soulmate.instance === 'object') {
return Soulmate.instance
}
Soulmate.instance = this
return this
}
}
export default Soulmate
当我们只需要一个类的唯一实例时,请使用单例模式。
原型模式
原型模式(prototype)基于具有默认属性值的现有对象创建新对象。
举个栗子,我们可以使用 clone
克隆方法,创建一个与其父对象相同品种的猫猫新实例。
js
class Cat {
constructor(color, type) {
this.color = color
this.type = type
}
clone() {
return new Cat(this.color, this.type)
}
}
export default Cat
当我们能且仅能在运行时实例化类时,请使用原型模式。
工厂模式
工厂模式(Factory)创建新对象,该对象委托给子类决定需要实例化的类。
举个栗子,MovieFactory
电影工厂决定创建哪种类型的电影。
js
// 电影工厂
class MovieFactory {
create(type) {
if (type === '岛国动作片') return new Movie(type, 9)
if (type === '科幻片') return new Movie(type, 99)
}
}
class Movie {
constructor(type, price) {
this.type = type
this.price = price
}
}
export default MovieFactory
当我们希望由子类决定创建的对象时,请使用工厂模式。
抽象工厂
抽象工厂模式(abstract factory)按主题创建的新对象,而不指定其具体的类。
js
// 前端框架工厂
function useFramework(lib) {
if (lib === 'Vue') return useVue
if (lib === 'React') return useReact
return use$
}
function useVue() {
return new Vue()
}
function useReact() {
return new React()
}
function use$() {
return new $()
}
class Vue {
init() {
return 'Vue'
}
}
class React {
init() {
return 'React'
}
}
class $ {
init() {
return 'jQuery'
}
}
export default useFramework
当系统应该独立于其生成的内容的结构或表示方式时,请使用抽象工厂模式。
结构型模式
结构型模式是一种设计模式类别,用于解决对象和类场景的组合相关的常见问题。
适配器模式
适配器模式(adapter)允许类"梦幻联动",将一个类接口创建为另一个类接口。
举个栗子,我们使用了一个 AxiosAdapter
适配器,可以在当前的系统中使用遗留的 request()
方法,且可以支持新版的 NodeFetch
。
js
class Fetch {
constructor(url) {
this.url = url
}
request() {
return `https://${this.url}`
}
}
class NodeFetch {
constructor(url) {
this.url = url
}
nodeRequest() {
return `https://${this.url}.com`
}
}
class AxiosAdapter {
constructor(nodeFetch) {
this.nodeFetch = nodeFetch
}
request() {
return this.nodeFetch.nodeRequest()
}
}
export { Fetch, NodeFetch, AxiosAdapter }
当我们需要使用现有类,但它们之间的接口不匹配时,请使用适配器模式。
桥接模式
桥接模式(bridge)允许我们的类中的一个接口根据我们接收的实例,以及我们需要返回的实例,构建不同的实现。
举个栗子,我们在 Soldier
士兵类型和 Weapon
武器类型之间建桥,这样我们就可以将 weapon
武器实例精准传递给 soldier
士兵实例。
js
class Soldier {
constructor(weapon) {
this.weapon = weapon
}
}
class SuperSoldier extends Soldier {
constructor(weapon) {
super(weapon)
}
attack() {
return `超级士兵,武器:${this.weapon.get()}`
}
}
class IronMan extends Soldier {
constructor(weapon) {
super(weapon)
}
attack() {
return `钢铁侠,武器:${this.weapon.get()}`
}
}
class Weapon {
constructor(type) {
this.type = type
}
get() {
return this.type
}
}
class Shield extends Weapon {
constructor() {
super('防护罩')
}
}
class Rocket extends Weapon {
constructor() {
super('火箭')
}
}
export { SuperSoldier, IronMan, Shield, Rocket }
当我们需要在运行时从抽象中使用特定实现时,请使用桥接模式。
组合模式
组合模式(composite)允许创建属性具有原始类型元素或对象集合的对象。集合中每个元素本身可以包含其他集合,从而创建深度嵌套的结构。
举个栗子,我们正在创建一个计算设备子系统,该系统存储在一个 Cabinet
主机箱中,每个元素可以是不同的实例。
js
// 设备
class Equipment {
getPrice() {
return this.price || 0
}
getName() {
return this.name
}
setName(name) {
this.name = name
}
}
class Pattern extends Equipment {
constructor() {
super()
this.equipments = []
}
add(equipment) {
this.equipments.push(equipment)
}
getPrice() {
return this.equipments
.map(equipment => {
return equipment.getPrice()
})
.reduce((a, b) => {
return a + b
})
}
}
class Cabinet extends Pattern {
constructor() {
super()
this.setName('主机箱')
}
}
// 各种设备
class FloppyDisk extends Equipment {
constructor() {
super()
this.setName('软盘')
this.price = 70
}
}
class HardDrive extends Equipment {
constructor() {
super()
this.setName('硬件驱动')
this.price = 250
}
}
class Memory extends Equipment {
constructor() {
super()
this.setName('内存')
this.price = 280
}
}
export { Cabinet, FloppyDisk, HardDrive, Memory }
当我们想要表示对象的层次结构时,请使用组合模式。
装饰器模式
装饰器模式(decorator)允许在运行时动态扩展对象的行为。
举个栗子,我们使用装饰器来扩展应用的通知行为。
js
class Notification {
constructor(kind) {
this.kind = kind || 'App'
}
getInfo() {
return `${this.kind} 发来通知`
}
}
// 公众号通知
class WeChatNotification extends Notification {
constructor() {
super('公众号')
}
setNotification(msg) {
this.message = msg
}
getInfo() {
return `${super.getInfo()},具体消息:${this.message}`
}
}
// B 站通知
class BiliBiliNotification extends Notification {
constructor() {
super('bilibili')
}
getInfo() {
return super.getInfo()
}
}
export { WeChatNotification, BiliBiliNotification }
当我们想要在运行时扩展对象,而不影响其他对象时,请使用装饰器模式。
外观模式/门面模式
外观模式(facade)为子系统中的一组接口提供了一个简化接口。外观模式定义了更高阶的接口,使子系统更易用。
举个栗子,我们创建一个简单的 Cart
购物车接口,它将若干子系统(比如 Discount/Shipping/Fees
等)的所有复杂性抽象出来。
js
// 购物车
class Cart {
constructor() {
this.discount = new Discount()
this.shipping = new Shipping()
this.fees = new Fees()
}
calc(price) {
price = this.discount.calc(price)
price = this.fees.calc(price)
price += this.shipping.calc()
return price
}
}
// 折扣
class Discount {
calc(value) {
return value * 0.85
}
}
// 物流
class Shipping {
calc() {
return 500
}
}
// 费用
class Fees {
calc(value) {
return value * 1.1
}
}
export default Cart
当我们想为复杂子系统提供简单接口时,请使用外观模式。
享元模式
享元模式(flyweight)通过有效共享一大坨颗粒化对象来节省内存。共享的享元对象是不可变的,换而言之,它们无法修改,因为它们表示与其他对象共享的特征。
举个栗子,我们正在管理和创建某种食谱或烹饪应用程序的原料。
js
class Ingredient {
constructor(name) {
this.name = name
}
getInfo() {
return `我是${this.name}原材料`
}
}
class Ingredients {
constructor() {
this.ingredients = {}
}
create(name) {
let ingredient = this.ingredients[name]
if (ingredient) return ingredient
this.ingredients[name] = new Ingredient(name)
return this.ingredients[name]
}
}
export { Ingredients }
当应用程序使用一大坨迷你对象,且其存储成本昂贵,或者其本体不重要时,请使用享元模式。
代理模式
代理模式(proxy)为另一个对象提供一个代理或占位符对象,并控制该对象的读写。
举个栗子,我们使用代理模式,来限制飞行员的年龄。
js
class Plane {
fly() {
return '乌鸦做飞机'
}
}
class PilotProxy {
constructor(pilot) {
this.pilot = pilot
}
fly() {
return this.pilot.age < 18 ? `未成年` : new Plane().fly()
}
}
class Pilot {
constructor(age) {
this.age = age
}
}
export { Plane, PilotProxy, Pilot }
当对象受到严重约束,且无法履行其职责时,请使用代理模式。
高潮总结
本文主要科普了创建型模式和结构型模式,行为型模式则会在下篇完整补充,欢迎关注"前端俱乐部",随时获取最新前端前沿资讯。
本文轻科普了两大类设计模式,包括但不限于:
- 创建型模式
- 单例模式
- 原型模式
- 工厂模式
- 抽象工厂模式
- 结构型模式
- 适配器模式
- 桥接模式
- 组合模式
- 装饰器模式
- 外观模式
- 享元模式
- 代理模式
本期话题是 ------ 你在实际开发中最常使用的是哪种设计模式?欢迎在本文下方自由言论,文明共享。
长期关注"前端俱乐部",坚持阅读,自律打卡,每天一次,进步一点。谢谢大家的点赞,掰掰~