Appearance
设计模式
大纲
设计原则
- 单一职责原则 Single Responsibility Principle(SRP)
- 开放封闭原则 Open Closed Principle(OCP)
- 里氏替换原则 Liskov Substitution Principle(LSP)
- 接口隔离原则 Interface Segregation Principle(ISP)
- 依赖倒置原则 Dependency Inversion Principle(DIP)
- 迪米特法则(最少知识原则) DP
- 合成复合原则 CSP
设计模式
- 创建型
- 工厂、单例、建造者
- 结构型
- 代理、适配器、桥接、装饰、外观、享元、组合
- 行为型
- 观察者、迭代器、模板、策略、命令、职责链、状态、访问者、中介者、备忘录、解释器
五大原则 单一职责原则-SRP
SRP
:单一职责原则,一个对象只做一件事
js
class UserOptions {
// 修改用户信息、密码
updateUserInfo(user, type) {
if (type === 'username') {
this.user.username = user.username
} else if (type === 'password') {
this.user.password = user.password
}
}
}
const userOpts = new UserOptions()
userOpts.updateUserInfo({ username: 'admin' }, 'username')
js
class UserOptionsSRP {
updateUserName(username) {
this.user.username = username
}
updatePassword(password) {
this.user.password = password
}
}
const userOptsSPR = new UserOptionsSRP()
userOptsSPR.updateUserName('admin')
userOptsSPR.updatePassword('123456')
使用前和使用后对比:如果需要新增修改字段,在修改前,就需要新增 else if
条件,而修改后,只需要新增一个方法即可
五大原则 开放封闭原则-OCP
什么是开和闭?
- 对扩展开放,对修改封闭
- 进行解耦
我们在设计一个功能时,如何考虑让核心功能是一个扩展的逻辑,而不是修改的逻辑
- slot 插槽
- compose 组合
- webpack,redux,axios 插件
例如:需要在 process 前去增加一些 before、after 的处理逻辑
js
// 不利于扩展
const process = ({ before, after }) => {
if (before) {
console.log('this is a before')
}
console.log('this is a process')
if (after) {
console.log('this is a after')
}
}
js
const newProcess = () => {
console.log('this is a process')
}
// 使用高阶函数实现了开放封闭原则
const compose = ({ beforeFn, afterFn }) => {
return function (fn) {
beforeFn && beforeFn()
fn()
afterFn && afterFn()
}
}
compose({
beforeFn: () => console.log('this is a before'),
afterFn: () => console.log('this is a after')
})(newProcess)
五大原则 里氏替换原则-LSP
超类的对象必须能够被其子类的对象替换,而不影响正确性
例如:
js
class Animal {}
class Cat extends Animal {
eatFish() {
console.log('吃鱼')
}
}
class Dog extends Animal {
eatBone() {
console.log('吃骨头')
}
}
function eat(animal) {
if (animal instanceof Cat) {
animal.eatFish()
} else if (animal instanceof Dog) {
animal.eatBone()
}
}
eat(new Cat())
eat(new Dog())
js
class Animal {}
class Cat extends Animal {
eat() {
console.log('吃鱼')
}
}
class Dog extends Animal {
eat() {
console.log('吃骨头')
}
}
function eat(animal) {
animal.eat()
}
eat(new Cat())
eat(new Dog())
注意:
- 在设计子类时,可以完整地继承父亲的职责,而不是去修改
- 组合的方式,可能会优于继承
- 多考虑多态逻辑的实现和应用
五大原则 接口隔离原则-ISP
意义和目的:尽量减少接口之间的耦合,在大型的软件架构设计中,多个接口的组合使用,好过一个大而全的接口
例子:
js
class Vehicle {
drive() {
console.log('driving...')
}
fly() {
console.log('flying...')
}
}
class Car extends Vehicle {
drive() {
console.log('driving car...')
}
fly() {
console.log('car can not fly...')
}
}
class Plane extends Vehicle {
drive() {
console.log('plane can not drive...')
}
fly() {
console.log('plane is flying...')
}
}
new Car().fly()
js
class Vehicle {}
class Car extends Vehicle {
drive() {
console.log('driving car...')
}
}
class Plane extends Vehicle {
fly() {
console.log('plane is flying...')
}
}
new Car().fly()
注意:
- 接口尽量小
- 接口要高内聚
五大原则 依赖倒置原则-DIP
核心:减少高层模块和底层模块之间的耦合
- 高层模块不应该直接依赖低层模块,两者都应该依赖其抽象;
- 抽象不应该依赖细节,细节应该依赖抽象。
例子:模拟邮件收发服务
js
// 高层模块
class MessageService {
constructor() {
this.emailSender = new EmailSender()
this.imSender = new IMSender()
// 当需要扩展更多的方法时,需要去修改高层模块,高度耦合,需要将 send 方法进行抽象解耦
}
sendingEmail(message) {
this.emailSender.send(message)
}
sendingIM(message) {
this.imSender.send(message)
}
}
// 底层模块
class EmailSender {
send(message) {
console.log('send email:', message)
}
}
class IMSender {
send(message) {
console.log('send im:', message)
}
}
js
// 抽象模块
class MessageSender {
send() {
throw new Error('not implemented')
}
}
// 高层模块
class MessageService {
constructor(sender) {
this.sender = sender
}
sendMessage(message) {
this.sender.send(message)
}
}
// 底层模块
class EmailSender extends MessageSender {
send(message) {
console.log('send email:', message)
}
}
class IMSender extends MessageSender {
send(message) {
console.log('send im:', message)
}
}
new MessageService(new EmailSender()).sendMessage('hello')
new MessageService(new IMSender()).sendMessage('hello')
设计模式 创建型-建造者模式
建造者模式也叫构造器模式,就是分步骤构造一个复杂的对象
简单的建造者模式:
js
function createUser(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
const user = new createUser('admin', 18, 'male')
复杂的建造者模式:
js
function Car() {}
Car.prototype.init = function ({ door, options, color }) {
this.initDoor(door).initOptions(options).initColor(color)
return this
}
Car.prototype.initDoor = function (door) {
this.door = door
return this
}
Car.prototype.initOptions = function (options) {
this.options = options
return this
}
Car.prototype.initColor = function (color) {
this.color = color
return this
}
const car = new Car().init({
door: 4,
options: ['自动', '手动'],
color: '白色'
})
const car2 = new Car()
.initDoor(4)
.initOptions(['自动', '手动'])
.initColor('白色')
console.log(car) // Car { door: 4, options: [ '自动', '手动' ], color: '白色' }
console.log(car2) // Car { door: 4, options: [ '自动', '手动' ], color: '白色' }
设计模式 创建型-工厂模式
将创建的对象的过程,进行单独封装
假设我们有一个简单的场景,需要创建不同类型的汽车对象,比如 Sedan 和 SUV。
js
// 定义汽车的基类
class Car {
constructor(type) {
this.type = type
}
drive() {
console.log(`Driving a ${this.type}`)
}
}
// 定义具体的汽车类
class Sedan extends Car {
constructor() {
super('Sedan')
}
}
class SUV extends Car {
constructor() {
super('SUV')
}
}
// 定义汽车工厂
class CarFactory {
createCar(type) {
switch (type) {
case 'sedan':
return new Sedan()
case 'suv':
return new SUV()
default:
throw new Error('Unknown car type')
}
}
}
// 使用工厂创建汽车对象
const factory = new CarFactory()
const sedan = factory.createCar('sedan')
const suv = factory.createCar('suv')
sedan.drive() // 输出: Driving a Sedan
suv.drive() // 输出: Driving a SUV
设计模式 创建型-单例模式
单例模式就是全局只有一个实例,并提供一个访问它的全局访问点。例如:window 对象、global
- 好处
- 全局唯一
- 可以实现状态管理
- 坏处
- 副作用太大
- 不符合单一职责原则
js
let singleton
function Singleton() {
if (!singleton) {
singleton = this
}
return singleton
}
Singleton.prototype.getName = function () {
console.log('单例模式')
}
const singletonA = new Singleton()
const singletonB = new Singleton()
console.log(singletonA === singletonB) // true
js
function Singleton() {
this.instance = null
}
Singleton.getInstance = function () {
if (!this.instance) {
this.instance = new Singleton()
}
}
Singleton.prototype.getName = function () {
console.log('单例模式')
}
const singletonA = Singleton.getInstance()
const singletonB = Singleton.getInstance()
console.log(singletonA === singletonB) // true
js
const Singleton = (function () {
let instance
function createInstance() {
instance = '单例'
return instance
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance()
}
console.log(instance)
return instance
}
}
})()
const singletonA = Singleton.getInstance()
const singletonB = Singleton.getInstance()
console.log(singletonA === singletonB, singletonA)
js
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this
}
return Singleton.instance
}
}
const singletonA = new Singleton()
const singletonB = new Singleton()
console.log(singletonA === singletonB)
设计模式 结构型-装饰器模式
装饰器模式就是给对象添加一些额外的功能,而不改变其原有的功能。例如:react中的 withRouter 方法,给组件添加路由信息,也是装饰器的应用
设计模式 结构型-适配器模式
适配器模式就是将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
例如:在 nestjs 中,可以使用 express 的内容
设计模式 结构型-代理模式
代理模式就是为另一个对象提供一种代理以控制对这个对象的访问。
设计模式 结构型-观察者模式
观察者模式:当对象存在一对多的关系时,则使用观察者模式。当一个对象被修改时,我需要通知其他对象
js
// 被观察者
class Subject {
constructor() {
this.deps = []
// state状态发生变化,通知到所有的 observer
this.state = 0
}
// 添加观察者
attach(obs) {
this.deps.push(obs)
}
// 状态发生变化
setState(newState) {
this.state = newState
this.notifyAllObservers()
}
// 通知所有观察者
notifyAllObservers() {
this.deps.forEach((dep) => {
dep.run(this.state)
})
}
}
// 观察者
class Observer {
constructor(subject) {
this.subject = subject
this.subject.attach(this)
}
run() {
throw new Error()
}
}
// 观察者1
class Observer1 extends Observer {
constructor(subject) {
super(subject)
}
run(data) {
console.log(`Observer1 data: ${data}`, this.subject.state)
}
}
// 观察者2
class Observer2 extends Observer {
constructor(subject) {
super(subject)
}
run(data) {
console.log(`Observer2 data: ${data}`, this.subject.state)
}
}
// 测试
const subject = new Subject()
const obs1 = new Observer1(subject)
const obs2 = new Observer2(subject)
subject.setState(15)
设计模式 行为型-发布订阅模式
发布订阅模式:当一个对象发生改变时,所有依赖于它的对象都会收到通知并自动更新。
发布订阅和观察者模式的区别:
- 观察者模式:强耦合,是一对一或一对多的关系,即一个具体的Subject对象与它的观察者们直接关联。每个观察者都明确知道它所要观察的Subject。
- 发布订阅模式:无耦合,是多对多的关系,发布者和订阅者之间没有直接联系,而是通过一个中间件(通常称为事件总线或消息代理)进行交互。
简单实现:
js
const emitter = (function () {
var deps = {}
return {
on: function (type, cb) {
deps[type] = deps[type] || []
deps[type].push(cb)
},
emit: function (type, ...rest) {
deps[type] instanceof Array &&
deps[type].forEach((cb) => cb.apply(null, rest))
},
off: function (type, cb) {
if (!deps[type]) return
let index = deps[type].findIndex((item) => item === cb)
if (index !== -1) {
deps[type].splice(index, 1)
}
}
}
})()
const handle1 = (data) => {
console.log('test 1', data)
}
emitter.on('test', handle1)
emitter.on('test', (data) => {
console.log('test 2', data)
})
setTimeout(() => {
emitter.emit('test', 'hello world')
emitter.off('test', handle1) // 取消订阅
emitter.emit('test', 'hello world') // 当 off 了handle1 后,handle1 不会再执行了
}, 1000)
实现redux:
js
export const createStore = (initState) => {
let state = initState
let listeners = []
// 订阅
const subscribe = (handler) => {
listeners.push(handler)
}
// 发布
const dispatch = (currentState) => {
state = currentState
listeners.forEach((fn) => fn())
}
// 获取数据
const getState = () => {
return state
}
return {
subscribe,
dispatch,
getState
}
}
export const store = createStore()
js
import { useEffect, useState } from 'react'
import { store } from './redux'
function App() {
const [list, setList] = useState([])
useEffect(() => {
store.subscribe(() => {
setList(store.getState())
})
}, [])
const handleClick = () => {
store.dispatch(['html', 'css', 'js'])
}
return (
<div>
{list.map((item, index) => {
return <div key={index}>{item}</div>
})}
<button onClick={handleClick}>click</button>
</div>
)
}
export default App
设计模式 行为型-策略模式
策略模式就是定义一系列的算法,把它们一个个封装起来,并且使他们可以相互替换。可以简化 if else 逻辑,提高代码的可读性
js
function getPrice(date, originPrice) {
if (date === '10-01') {
return originPrice * 0.8;
} else if (date === '10-02') {
return originPrice * 0.9;
} else {
return originPrice;
}
}
js
const priceMap = {
'10-01': (originPrice) => {
return originPrice * 0.8;
},
'10-02': (originPrice) => {
return originPrice * 0.9;
},
}
function getPrice(date, originPrice) {
const priceFn = priceMap[date];
if (priceFn) {
return priceFn(originPrice);
} else {
return originPrice;
}
}