本文目录
[[toc]]
单例模式
单例模式 有几个重要的特点:
优点:
- 保证一个类只有一个实例。(与
module全局状态 相同 ) - 提供了一个全局访问点。
- 仅在首次请求单例对象时进行初始化。(与
module全局状态 区分 )
- 保证一个类只有一个实例。(与
缺点:
- 违反单一职责原则。
- 掩盖不良设计,程序各组件之间耦合度高。
- 在多线程环境中需要特殊处理,避免多次创建单例对象。
class Singleton {
/** 全局存储唯一的实例 */
private static instance: Singleton
/** 测试是否为同一实例 */
private connectionCount = 0
/** 支持使用 new 创建单例 */
constructor() {
if (!Singleton.instance) {
// 如果没有创建过实例,就使用 this 作为实例,保存到静态方法中
// 这里不能直接从静态方法中 getInstance ,否则会出现死循环
Singleton.instance = this
}
return Singleton.instance
}
/** 支持使用静态方法获取单例 */
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton()
}
return Singleton.instance
}
public incrementConnection() {
this.connectionCount++
return this.connectionCount
}
}
const instance1 = Singleton.getInstance()
const instance2 = Singleton.getInstance()
console.log('是否为同一实例', instance1 === instance2)
console.log('Connection count:', instance1.incrementConnection()) // 1
console.log('Connection count:', instance1.incrementConnection()) // 2
console.log('Connection count:', instance2.incrementConnection()) // 3工厂模式
工厂模式 本质上就是一个返回对象的函数,由于 JS 的特殊性,使用箭头函数要比工厂模式更加简单,而使用 new class 则比工厂函数更低内存开销。
优点:
- 提高了代码的可维护性和可扩展性。
- 通过工厂方法创建对象,避免了直接使用
new关键字,降低了代码耦合度。 - 可以根据需要返回不同类型的对象,增强了代码的灵活性。
缺点:
- 增加了系统的复杂性,需要额外编写工厂类或工厂方法。
- 可能会导致类的数量增加,增加系统的维护成本。
- 在某些情况下,可能会影响性能,因为需要通过工厂方法创建对象。
interface Product {
operation: () => string
}
class ConcreteProductA implements Product {
public operation(): string {
return 'Result of ConcreteProductA'
}
}
class ConcreteProductB implements Product {
public operation(): string {
return 'Result of ConcreteProductB'
}
}
function createProduct(type: string): Product {
if (type === 'A') {
return new ConcreteProductA()
} else if (type === 'B') {
return new ConcreteProductB()
} else {
throw new Error('Unknown product type')
}
}
const productA = Creator.createProduct('A')
console.log(productA.operation()) // Result of ConcreteProductA
const productB = Creator.createProduct('B')
console.log(productB.operation()) // Result of ConcreteProductB策略模式
策略模式 定义了一系列算法,并将每个算法封装起来,使它们可以互换使用。策略模式使得算法可以独立于使用它的客户端而变化。
优点:
- 你可以在运行时切换对象内的算法。
- 你可以将算法的实现和使用算法的代码隔离开来。
- 你可以使用组合来代替继承。
- 开闭原则。你无需对上下文进行修改就能够引入新的策略。
缺点:
- 如果你的算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式会让程序过于复杂。
- 客户端必须知晓策略间的不同——它需要选择合适的策略。
- 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象的完全相同,无需借助额外的类和接口来保持代码简洁。
interface Strategy {
execute: (a: number, b: number) => number
}
class ConcreteStrategyAdd implements Strategy {
public execute(a: number, b: number): number {
return a + b
}
}
class ConcreteStrategySubtract implements Strategy {
public execute(a: number, b: number): number {
return a - b
}
}
class ConcreteStrategyMultiply implements Strategy {
public execute(a: number, b: number): number {
return a * b
}
}
class Context {
private strategy: Strategy
constructor(strategy: Strategy) {
this.strategy = strategy
}
public setStrategy(strategy: Strategy) {
this.strategy = strategy
}
public executeStrategy(a: number, b: number): number {
return this.strategy.execute(a, b)
}
}
const addStrategy = new ConcreteStrategyAdd()
const context = new Context(addStrategy)
console.log('Add: ', context.executeStrategy(1, 2)) // 1 + 2 = 3
const subtractStrategy = new ConcreteStrategySubtract()
context.setStrategy(subtractStrategy)
console.log('Subtract: ', context.executeStrategy(5, 3)) // 5 - 3 = 2
const multiplyStrategy = new ConcreteStrategyMultiply()
context.setStrategy(multiplyStrategy)
console.log('Multiply: ', context.executeStrategy(5, 2)) // 5 * 2 = 10代理模式
代理模式 就是为对象提供另一个 API 相同的替代品,用以对对象行为进行控制
优点:
- 你可以在客户端毫无察觉的情况下控制服务对象。
- 如果客户端对服务对象的生命周期没有特殊要求,你可以对其生命周期进行管理。
- 即使服务对象还未准备好或不存在,代理也可以正常工作。
- 开闭原则。你可以在不对服务对象或客户端做出修改的情况下创建新代理。
缺点:
- 代码可能会变得复杂,因为需要新建许多类。
- 服务响应可能会延迟。
interface ThirdPartyTVLib {
listVideos: () => string[]
getVideoInfo: (id: string) => string
downloadVideo: (id: string) => void
}
// 具体的第三方视频服务类,模拟从远程服务获取视频信息
class ThirdPartyTVClass implements ThirdPartyTVLib {
listVideos(): string[] {
console.log('Fetching list of videos from remote service...')
return ['Video1', 'Video2', 'Video3']
}
getVideoInfo(id: string): string {
console.log(`Fetching info for video ${id} from remote service...`)
return `Info about video ${id}`
}
downloadVideo(id: string): void {
console.log(`Downloading video ${id} from remote service...`)
}
}
// 缓存代理类,缓存第三方视频服务的数据
class CachedTVClass implements ThirdPartyTVLib {
private service: ThirdPartyTVLib
private listCache: string[] | null = null
private videoCache: { [key: string]: string } = {}
private needReset: boolean = false
constructor(service: ThirdPartyTVLib) {
this.service = service
}
// 缓存视频列表
listVideos(): string[] {
if (this.listCache === null || this.needReset) {
this.listCache = this.service.listVideos()
}
return this.listCache
}
// 缓存视频信息
getVideoInfo(id: string): string {
if (!this.videoCache[id] || this.needReset) {
this.videoCache[id] = this.service.getVideoInfo(id)
}
return this.videoCache[id]
}
// 缓存视频下载
downloadVideo(id: string): void {
if (!this.videoCache[id] || this.needReset) {
this.service.downloadVideo(id)
}
}
}
// 管理视频服务的类
class TVManager {
protected service: ThirdPartyTVLib
constructor(service: ThirdPartyTVLib) {
this.service = service
}
// 渲染视频页面
renderVideoPage(id: string): void {
const info = this.service.getVideoInfo(id)
console.log(`Rendering video page with info: ${info}`)
}
// 渲染视频列表面板
renderListPanel(): void {
const list = this.service.listVideos()
console.log(`Rendering list panel with videos: ${list.join(', ')}`)
}
// 响应用户输入
reactOnUserInput(): void {
this.renderVideoPage('Video1')
this.renderListPanel()
}
}
// 应用程序类,初始化视频管理器
class Application {
init(): void {
const aTVService = new ThirdPartyTVClass()
const aTVProxy = new CachedTVClass(aTVService)
const manager = new TVManager(aTVProxy)
manager.reactOnUserInput()
}
}
const app = new Application()
app.init()发布/订阅模式
发布/订阅模式 允许对象通过发布和订阅事件进行通信,而无需显式地相互引用。
优点:
- 提高了组件之间的松耦合性。
- 使得事件的发送者和接收者可以独立演化。
- 提供了一种灵活的方式来处理事件和消息。
缺点:
- 可能会导致系统的复杂性增加。
- 如果不加以管理,可能会导致内存泄漏。
- 调试和跟踪事件流可能会变得困难。
class PubSub {
constructor() {
this.messages = {}
this.listeners = {}
}
// 添加发布者
publish(type, content) {
const existContent = this.messages[type]
if (!existContent) {
this.messages[type] = []
}
this.messages[type].push(content)
}
// 添加订阅者
subscribe(type, cb) {
const existListener = this.listeners[type]
if (!existListener) {
this.listeners[type] = []
}
this.listeners[type].push(cb)
}
// 通知
notify(type) {
const messages = this.messages[type]
const subscribers = this.listeners[type] || []
subscribers.forEach((cb, index) => cb(messages[index]))
}
}
class Publisher {
constructor(name, context) {
this.name = name
this.context = context
}
publish(type, content) {
this.context.publish(type, content)
}
}
class Subscriber {
constructor(name, context) {
this.name = name
this.context = context
}
subscribe(type, cb) {
this.context.subscribe(type, cb)
}
}
const TYPE_A = 'music'
const TYPE_B = 'movie'
const TYPE_C = 'novel'
const pubsub = new PubSub()
const publisherA = new Publisher('publisherA', pubsub)
publisherA.publish(TYPE_A, 'we are young')
publisherA.publish(TYPE_B, 'the silicon valley')
const publisherB = new Publisher('publisherB', pubsub)
publisherB.publish(TYPE_A, 'stronger')
const publisherC = new Publisher('publisherC', pubsub)
publisherC.publish(TYPE_C, 'a brief history of time')
const subscriberA = new Subscriber('subscriberA', pubsub)
subscriberA.subscribe(TYPE_A, (res) => {
console.log('subscriberA received', res)
})
const subscriberB = new Subscriber('subscriberB', pubsub)
subscriberB.subscribe(TYPE_C, (res) => {
console.log('subscriberB received', res)
})
const subscriberC = new Subscriber('subscriberC', pubsub)
subscriberC.subscribe(TYPE_B, (res) => {
console.log('subscriberC received', res)
})
pubsub.notify(TYPE_A)
pubsub.notify(TYPE_B)
pubsub.notify(TYPE_C)观察者模式
观察者模式 允许定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。
优点:
- 开闭原则。你无需修改发布者代码就能引入新的订阅者(如果是发布者接口则可轻松引入发布者类)。
- 你可以在运行时建立对象之间的联系。
缺点:
- 订阅者的通知顺序是随机的。
// 发布者基类包含订阅管理代码和通知方法。
class EventManager {
private listeners: { [eventType: string]: EventListener[] } = {}
// 订阅事件
public subscribe(eventType: string, listener: EventListener): void {
if (!this.listeners[eventType]) {
this.listeners[eventType] = []
}
this.listeners[eventType].push(listener)
}
// 取消订阅事件
public unsubscribe(eventType: string, listener: EventListener): void {
if (!this.listeners[eventType]) {
return
}
this.listeners[eventType] = this.listeners[eventType].filter(l => l !== listener)
}
// 通知所有订阅者
public notify(eventType: string, data: any): void {
if (!this.listeners[eventType]) {
return
}
this.listeners[eventType].forEach(listener => listener.update(data))
}
}
// 具体发布者包含一些订阅者感兴趣的实际业务逻辑。
class Editor {
public events: EventManager
private file: File | null = null
constructor() {
this.events = new EventManager()
}
// 打开文件并通知订阅者
public openFile(path: string): void {
this.file = new File(path)
this.events.notify('open', this.file.name)
}
// 保存文件并通知订阅者
public saveFile(): void {
if (this.file) {
this.file.write()
this.events.notify('save', this.file.name)
}
}
}
// 订阅者接口
interface EventListener {
update: (filename: string) => void
}
// 具体订阅者对更新消息做出响应
class LoggingListener implements EventListener {
private log: File
private message: string
constructor(logFilename: string, message: string) {
this.log = new File(logFilename)
this.message = message
}
public update(filename: string): void {
this.log.write(this.message.replace('%s', filename))
}
}
class EmailAlertsListener implements EventListener {
private email: string
private message: string
constructor(email: string, message: string) {
this.email = email
this.message = message
}
public update(filename: string): void {
// 模拟发送邮件
console.log(`Email to ${this.email}: ${this.message.replace('%s', filename)}`)
}
}
// 应用程序配置发布者和订阅者
class Application {
public config(): void {
const editor = new Editor()
const logger = new LoggingListener(
'/path/to/log.txt',
'有人打开了文件:%s'
)
editor.events.subscribe('open', logger)
const emailAlerts = new EmailAlertsListener(
'admin@example.com',
'有人更改了文件:%s'
)
editor.events.subscribe('save', emailAlerts)
}
}