行为型设计模式
大约 23 分钟
行为型设计模式
行为模式负责对象间的高效沟通和职责委派。
职责链模式 (Chain of Responsibility)
允许将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。
适合应用场景:
- 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式。该模式能将多个处理者连接成一条链。接收到请求后,它会“询问”每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。
- 当必须按顺序执行多个处理者时,可以使用责任链模式。无论以何种顺序将处理者连接成一条链,所有请求都会严格按照顺序通过链上的处理者。
- 如果所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。如果在处理者类中有对引用成员变量的设定方法,将能动态地插入和移除处理者,或者改变其顺序。
优先:
- 可以控制请求处理的顺序。
- 单一职责原则。可对发起操作和执行操作的类进行解耦。
- 开闭原则。可以在不更改现有代码的情况下在程序中新增处理者。
缺点:部分请求可能未被处理。
代码示例
js
class AbstractHandler {
setNext(handler) {
this.nextHandler = handler
return handler
}
handle(request) {
if (this.nextHandler) {
return this.nextHandler.handle(request)
}
return null
}
}
class MonkeyHandler extends AbstractHandler {
handle(request) {
if (request === 'Banana') {
return `Monkey: I'll eat the ${request}.`
}
return super.handle(request)
}
}
class SquirrelHandler extends AbstractHandler {
handle(request) {
if (request === 'Nut') {
return `Squirrel: I'll eat the ${request}.`
}
return super.handle(request)
}
}
class DogHandler extends AbstractHandler {
handle(request) {
if (request === 'MeatBall') {
return `Dog: I'll eat the ${request}.`
}
return super.handle(request)
}
}
function clientCode(handler) {
const foods = ['Nut', 'Banana', 'Cup of coffee']
for (const food of foods) {
console.log(`Client: Who wants a ${food}?`)
const result = handler.handle(food)
if (result) {
console.log(` ${result}`)
} else {
console.log(` ${food} was left untouched.`)
}
}
}
const monkey = new MonkeyHandler()
const squirrel = new SquirrelHandler()
const dog = new DogHandler()
monkey.setNext(squirrel).setNext(dog)
console.log('Chain: Monkey > Squirrel > Dog\n')
clientCode(monkey)
console.log('Subchain: Squirrel > Dog\n')
clientCode(squirrel)
ts
interface Handler {
setNext(handler: Handler): Handler
handle(request: string): string
}
abstract class AbstractHandler implements Handler {
private nextHandler: Handler
public setNext(handler: Handler): Handler {
this.nextHandler = handler
return handler
}
public handle(request: string): string {
if (this.nextHandler) {
return this.nextHandler.handle(request)
}
return null
}
}
class MonkeyHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'Banana') {
return `Monkey: I'll eat the ${request}.`
}
return super.handle(request)
}
}
class SquirrelHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'Nut') {
return `Squirrel: I'll eat the ${request}.`
}
return super.handle(request)
}
}
class DogHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'MeatBall') {
return `Dog: I'll eat the ${request}.`
}
return super.handle(request)
}
}
function clientCode(handler: Handler) {
const foods = ['Nut', 'Banana', 'Cup of coffee']
for (const food of foods) {
console.log(`Client: Who wants a ${food}?`)
const result = handler.handle(food)
if (result) {
console.log(` ${result}`)
} else {
console.log(` ${food} was left untouched.`)
}
}
}
const monkey = new MonkeyHandler()
const squirrel = new SquirrelHandler()
const dog = new DogHandler()
monkey.setNext(squirrel).setNext(dog)
console.log('Chain: Monkey > Squirrel > Dog\n')
clientCode(monkey)
console.log('Subchain: Squirrel > Dog\n')
clientCode(squirrel)
命令模式 (Command)
可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。
适合应用场景:
- 需要通过操作来参数化对象,可使用命令模式。该模式可将特定的方法调用转化为独立对象。可以将命令作为方法的参数进行传递、将命令保存在其他对象中,或者在运行时切换已连接的命令等。
- 将操作放入队列中、操作的执行或者远程执行操作,可使用命令模式。同其他对象一样,命令也可以实现序列化(序列化的意思是转化为字符串),从而能方便地写入文件或数据库中。一段时间后,该字符串可被恢复成为最初的命令对象。使用同样的方式,还可以将命令放入队列、记录命令或者通过网络发送命令。
- 实现操作回滚功能,可使用命令模式。为了能够回滚操作,需要实现已执行操作的历史记录功能。命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。
优点:
- 单一职责原则。可以解耦触发和执行操作的类。
- 开闭原则。可以在不修改已有客户端代码的情况下在程序中创建新的命令。
- 可以实现撤销和恢复功能。
- 可以实现操作的延迟执行。
- 可以将一组简单命令组合成一个复杂命令。
缺点:代码可能会变得更加复杂,因为在发送者和接收者之间增加了一个全新的层次。
代码示例
js
class SimpleCommand {
constructor(payload) {
this.payload = payload
}
execute() {
console.log(
`SimpleCommand: See, I can do simple things like printing (${this.payload})`
)
}
}
class ComplexCommand {
constructor(receiver, a, b) {
this.receiver = receiver
this.a = a
this.b = b
}
execute() {
console.log(
'ComplexCommand: Complex stuff should be done by a receiver object.'
)
this.receiver.doSomething(this.a)
this.receiver.doSomethingElse(this.b)
}
}
class Receiver {
doSomething(a) {
console.log(`Receiver: Working on (${a}.)`)
}
doSomethingElse(b) {
console.log(`Receiver: Also working on (${b}.)`)
}
}
class Invoker {
setOnStart(command) {
this.onStart = command
}
setOnFinish(command) {
this.onFinish = command
}
doSomethingImportant() {
console.log('Invoker: Does anybody want something done before I begin?')
if (this.isCommand(this.onStart)) {
this.onStart.execute()
}
console.log('Invoker: ...doing something really important...')
console.log('Invoker: Does anybody want something done after I finish?')
if (this.isCommand(this.onFinish)) {
this.onFinish.execute()
}
}
isCommand(object) {
return object.execute !== undefined
}
}
const invoker = new Invoker()
invoker.setOnStart(new SimpleCommand('Say Hi!'))
const receiver = new Receiver()
invoker.setOnFinish(new ComplexCommand(receiver, 'Send email', 'Save report'))
invoker.doSomethingImportant()
ts
interface Command {
execute(): void
}
class SimpleCommand implements Command {
private payload: string
constructor(payload: string) {
this.payload = payload
}
public execute(): void {
console.log(`SimpleCommand: See, I can do simple things like printing (${this.payload})`)
}
}
class ComplexCommand implements Command {
private receiver: Receiver
private a: string
private b: string
constructor(receiver: Receiver, a: string, b: string) {
this.receiver = receiver
this.a = a
this.b = b
}
public execute(): void {
console.log('ComplexCommand: Complex stuff should be done by a receiver object.')
this.receiver.doSomething(this.a)
this.receiver.doSomethingElse(this.b)
}
}
class Receiver {
public doSomething(a: string): void {
console.log(`Receiver: Working on (${a}.)`)
}
public doSomethingElse(b: string): void {
console.log(`Receiver: Also working on (${b}.)`)
}
}
class Invoker {
private onStart: Command
private onFinish: Command
public setOnStart(command: Command): void {
this.onStart = command
}
public setOnFinish(command: Command): void {
this.onFinish = command
}
public doSomethingImportant(): void {
console.log('Invoker: Does anybody want something done before I begin?')
if (this.isCommand(this.onStart)) {
this.onStart.execute()
}
console.log('Invoker: ...doing something really important...')
console.log('Invoker: Does anybody want something done after I finish?')
if (this.isCommand(this.onFinish)) {
this.onFinish.execute()
}
}
private isCommand(object): object is Command {
return object.execute !== undefined
}
}
const invoker = new Invoker()
invoker.setOnStart(new SimpleCommand('Say Hi!'))
const receiver = new Receiver()
invoker.setOnFinish(new ComplexCommand(receiver, 'Send email', 'Save report'))
invoker.doSomethingImportant()
迭代器模式 (Iterator)
能在不暴露集合底层表现形式(列表、栈和树等)的情况下,遍历集合中所有的元素。
适合应用场景:
- 当集合背后为复杂的数据结构,且对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。
- 希望减少程序中重复的遍历代码,可以使用迭代器模式。
- 希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。
优点:
- 单一职责原则。通过将体积庞大的遍历算法代码抽取为独立的类,可对客户端代码和集合进行整理。
- 开闭原则。可实现新型的集合和迭代器并将其传递给现有代码,无需修改现有代码。
- 可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态。
- 可以暂停遍历并在需要时继续。
缺点:
- 如果程序只与简单的集合进行交互,应用该模式可能会矫枉过正。
- 对于某些特殊集合,使用迭代器可能比直接遍历的效率低。
代码示例
js
class AlphabeticalOrderIterator {
constructor(collection, reverse = false) {
this.position = 0
this.reverse = false
this.collection = collection
this.reverse = reverse
if (reverse) {
this.position = collection.getCount() - 1
}
}
rewind() {
this.position = this.reverse ? this.collection.getCount() - 1 : 0
}
current() {
return this.collection.getItems()[this.position]
}
key() {
return this.position
}
next() {
const item = this.collection.getItems()[this.position]
this.position += this.reverse ? -1 : 1
return item
}
valid() {
if (this.reverse) {
return this.position >= 0
}
return this.position < this.collection.getCount()
}
}
class WordsCollection {
constructor() {
this.items = []
}
getItems() {
return this.items
}
getCount() {
return this.items.length
}
addItem(item) {
this.items.push(item)
}
getIterator() {
return new AlphabeticalOrderIterator(this)
}
getReverseIterator() {
return new AlphabeticalOrderIterator(this, true)
}
}
const collection = new WordsCollection()
collection.addItem('First')
collection.addItem('Second')
collection.addItem('Third')
const iterator = collection.getIterator()
console.log('Straight traversal:')
while (iterator.valid()) {
console.log(iterator.next())
}
console.log('Reverse traversal:')
const reverseIterator = collection.getReverseIterator()
while (reverseIterator.valid()) {
console.log(reverseIterator.next())
}
ts
interface Iterator<T> {
current(): T
next(): T
key(): number
valid(): boolean
rewind(): void
}
interface Aggregator {
getIterator(): Iterator<string>
}
class AlphabeticalOrderIterator implements Iterator<string> {
private collection: WordsCollection
private position: number = 0
private reverse: boolean = false
constructor(collection: WordsCollection, reverse: boolean = false) {
this.collection = collection
this.reverse = reverse
if (reverse) {
this.position = collection.getCount() - 1
}
}
public rewind() {
this.position = this.reverse ? this.collection.getCount() - 1 : 0
}
public current(): string {
return this.collection.getItems()[this.position]
}
public key(): number {
return this.position
}
public next(): string {
const item = this.collection.getItems()[this.position]
this.position += this.reverse ? -1 : 1
return item
}
public valid(): boolean {
if (this.reverse) {
return this.position >= 0
}
return this.position < this.collection.getCount()
}
}
class WordsCollection implements Aggregator {
private items: string[] = []
public getItems(): string[] {
return this.items
}
public getCount(): number {
return this.items.length
}
public addItem(item: string): void {
this.items.push(item)
}
public getIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this)
}
public getReverseIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this, true)
}
}
const collection = new WordsCollection()
collection.addItem('First')
collection.addItem('Second')
collection.addItem('Third')
const iterator = collection.getIterator()
console.log('Straight traversal:')
while (iterator.valid()) {
console.log(iterator.next())
}
console.log('Reverse traversal:')
const reverseIterator = collection.getReverseIterator()
while (reverseIterator.valid()) {
console.log(reverseIterator.next())
}
中介者模式 (Mediator)
减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。
适合应用场景:
- 当一些对象和其他对象紧密耦合以致难以对其进行修改时,可使用中介者模式。该模式让你将对象间的所有关系抽取成为一个单独的类,以使对于特定组件的修改工作独立于其他组件。
- 当组件因过于依赖其他组件而无法在不同应用中复用时,可使用中介者模式。
- 如果为了能在不同情景下复用一些基本行为,导致需要被迫创建大量组件子类时,可使用中介者模式。由于所有组件间关系都被包含在中介者中,因此无需修改组件就能方便地新建中介者类以定义新的组件合作方式。
优点:
- 单一职责原则。可以将多个组件间的交流抽取到同一位置,使其更易于理解和维护。
- 开闭原则。无需修改实际组件就能增加新的中介者。可以减轻应用中多个组件间的耦合情况。
- 可以更方便地复用各个组件。
缺点:一段时间后,中介者可能会演化成为上帝对象。
代码示例
js
class ConcreteMediator {
constructor(c1, c2) {
this.component1 = c1
this.component1.setMediator(this)
this.component2 = c2
this.component2.setMediator(this)
}
notify(sender, event) {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:')
this.component2.doC()
}
if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:')
this.component1.doB()
this.component2.doC()
}
}
}
class BaseComponent {
constructor(mediator) {
this.mediator = mediator
}
setMediator(mediator) {
this.mediator = mediator
}
}
class Component1 extends BaseComponent {
doA() {
console.log('Component 1 does A.')
this.mediator.notify(this, 'A')
}
doB() {
console.log('Component 1 does B.')
this.mediator.notify(this, 'B')
}
}
class Component2 extends BaseComponent {
doC() {
console.log('Component 2 does C.')
this.mediator.notify(this, 'C')
}
doD() {
console.log('Component 2 does D.')
this.mediator.notify(this, 'D')
}
}
const c1 = new Component1()
const c2 = new Component2()
const mediator = new ConcreteMediator(c1, c2)
console.log('Client triggers operation A.')
c1.doA()
console.log('Client triggers operation D.')
c2.doD()
ts
interface Mediator {
notify(sender: object, event: string): void
}
class ConcreteMediator implements Mediator {
private component1: Component1
private component2: Component2
constructor(c1: Component1, c2: Component2) {
this.component1 = c1
this.component1.setMediator(this)
this.component2 = c2
this.component2.setMediator(this)
}
public notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:')
this.component2.doC()
}
if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:')
this.component1.doB()
this.component2.doC()
}
}
}
class BaseComponent {
protected mediator: Mediator
constructor(mediator?: Mediator) {
this.mediator = mediator!
}
public setMediator(mediator: Mediator): void {
this.mediator = mediator
}
}
class Component1 extends BaseComponent {
public doA(): void {
console.log('Component 1 does A.')
this.mediator.notify(this, 'A')
}
public doB(): void {
console.log('Component 1 does B.')
this.mediator.notify(this, 'B')
}
}
class Component2 extends BaseComponent {
public doC(): void {
console.log('Component 2 does C.')
this.mediator.notify(this, 'C')
}
public doD(): void {
console.log('Component 2 does D.')
this.mediator.notify(this, 'D')
}
}
const c1 = new Component1()
const c2 = new Component2()
const mediator = new ConcreteMediator(c1, c2)
console.log('Client triggers operation A.')
c1.doA()
console.log('Client triggers operation D.')
c2.doD()
备忘录模式 (Memento)
允许在不暴露对象实现细节的情况下,保存和恢复对象之前的状态。
适合应用场景:
- 需要创建对象状态快照来恢复其之前的状态时,可以使用备忘录模式。该模式允许你复制对象中的全部状态(包括私有成员变量),并将其独立于对象进行保存。
- 当直接访问对象的成员变量、获取器或设置器将导致封装被突破时,可以使用备忘录模式。备忘录让对象自行负责创建其状态的快照。任何其他对象都不能读取快照,有效地保障了数据的安全性。
优点:
- 可以在不破坏对象封装情况的前提下创建对象状态快照。
- 可以通过让负责人维护原发器状态历史记录来简化原发器代码。
缺点:
- 如果客户端过于频繁地创建备忘录,程序将消耗大量内存。
- 负责人必须完整跟踪原发器的生命周期,这样才能销毁弃用的备忘录。
- 绝大部分动态编程语言(例如 PHP、Python 和 JavaScript)不能确保备忘录中的状态不被修改。
代码示例
js
class Originator {
constructor(state) {
this.state = state
console.log(`Originator: My initial state is: ${state}`)
}
doSomething() {
console.log("Originator: I'm doing something important.")
this.state = this.generateRandomString(30)
console.log(`Originator: and my state has changed to: ${this.state}`)
}
generateRandomString(length = 10) {
const charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
return Array.apply(null, { length })
.map(() => charSet.charAt(Math.floor(Math.random() * charSet.length)))
.join('')
}
save() {
return new ConcreteMemento(this.state)
}
restore(memento) {
this.state = memento.getState()
console.log(`Originator: My state has changed to: ${this.state}`)
}
}
class ConcreteMemento {
constructor(state) {
this.state = state
this.date = new Date().toISOString().slice(0, 19).replace('T', ' ')
}
getState() {
return this.state
}
getName() {
return `${this.date} / (${this.state.substr(0, 9)}...)`
}
getDate() {
return this.date
}
}
class Caretaker {
constructor(originator) {
this.mementos = []
this.originator = originator
}
backup() {
console.log("\nCaretaker: Saving Originator's state...")
this.mementos.push(this.originator.save())
}
undo() {
if (!this.mementos.length) {
return
}
const memento = this.mementos.pop()
console.log(`Caretaker: Restoring state to: ${memento.getName()}`)
this.originator.restore(memento)
}
showHistory() {
console.log("Caretaker: Here's the list of mementos:")
for (const memento of this.mementos) {
console.log(memento.getName())
}
}
}
const originator = new Originator('Super-duper-super-puper-super.')
const caretaker = new Caretaker(originator)
caretaker.backup()
originator.doSomething()
caretaker.backup()
originator.doSomething()
caretaker.backup()
originator.doSomething()
caretaker.showHistory()
console.log("\nClient: Now, let's rollback!\n")
caretaker.undo()
console.log('\nClient: Once more!\n')
caretaker.undo()
ts
class Originator {
private state: string
constructor(state: string) {
this.state = state
console.log(`Originator: My initial state is: ${state}`)
}
public doSomething(): void {
console.log("Originator: I'm doing something important.")
this.state = this.generateRandomString(30)
console.log(`Originator: and my state has changed to: ${this.state}`)
}
private generateRandomString(length: number = 10): string {
const charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
return Array.apply(null, { length })
.map(() => charSet.charAt(Math.floor(Math.random() * charSet.length)))
.join('')
}
public save(): Memento {
return new ConcreteMemento(this.state)
}
public restore(memento: Memento): void {
this.state = memento.getState()
console.log(`Originator: My state has changed to: ${this.state}`)
}
}
interface Memento {
getState(): string
getName(): string
getDate(): string
}
class ConcreteMemento implements Memento {
private state: string
private date: string
constructor(state: string) {
this.state = state
this.date = new Date().toISOString().slice(0, 19).replace('T', ' ')
}
public getState(): string {
return this.state
}
public getName(): string {
return `${this.date} / (${this.state.substr(0, 9)}...)`
}
public getDate(): string {
return this.date
}
}
class Caretaker {
private mementos: Memento[] = []
private originator: Originator
constructor(originator: Originator) {
this.originator = originator
}
public backup(): void {
console.log("\nCaretaker: Saving Originator's state...")
this.mementos.push(this.originator.save())
}
public undo(): void {
if (!this.mementos.length) {
return
}
const memento = this.mementos.pop()
console.log(`Caretaker: Restoring state to: ${memento.getName()}`)
this.originator.restore(memento)
}
public showHistory(): void {
console.log("Caretaker: Here's the list of mementos:")
for (const memento of this.mementos) {
console.log(memento.getName())
}
}
}
const originator = new Originator('Super-duper-super-puper-super.')
const caretaker = new Caretaker(originator)
caretaker.backup()
originator.doSomething()
caretaker.backup()
originator.doSomething()
caretaker.backup()
originator.doSomething()
caretaker.showHistory()
console.log("\nClient: Now, let's rollback!\n")
caretaker.undo()
console.log('\nClient: Once more!\n')
caretaker.undo()
观察者模式 (Observer)
允许定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。
适合应用场景:
- 当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时,可使用观察者模式。观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。
- 当应用中的一些对象必须观察其他对象时,可使用该模式。但仅能在有限时间内或特定情况下使用。订阅列表是动态的,因此订阅者可随时加入或离开该列表。
优点:
- 开闭原则。无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)。
- 可以在运行时建立对象之间的联系。
缺点:订阅者的通知顺序是随机的。
代码示例
js
class ConcreteSubject {
constructor() {
this.observers = []
}
attach(observer) {
const isExist = this.observers.includes(observer)
if (isExist) {
return console.log('Subject: Observer has been attached already.')
}
console.log('Subject: Attached an observer.')
this.observers.push(observer)
}
detach(observer) {
const observerIndex = this.observers.indexOf(observer)
if (observerIndex === -1) {
return console.log('Subject: Nonexistent observer.')
}
this.observers.splice(observerIndex, 1)
console.log('Subject: Detached an observer.')
}
notify() {
console.log('Subject: Notifying observers...')
for (const observer of this.observers) {
observer.update(this)
}
}
someBusinessLogic() {
console.log("\nSubject: I'm doing something important.")
this.state = Math.floor(Math.random() * (10 + 1))
console.log(`Subject: My state has just changed to: ${this.state}`)
this.notify()
}
}
class ConcreteObserverA {
update(subject) {
if (subject instanceof ConcreteSubject && subject.state < 3) {
console.log('ConcreteObserverA: Reacted to the event.')
}
}
}
class ConcreteObserverB {
update(subject) {
if (
subject instanceof ConcreteSubject &&
(subject.state === 0 || subject.state >= 2)
) {
console.log('ConcreteObserverB: Reacted to the event.')
}
}
}
const subject = new ConcreteSubject()
const observer1 = new ConcreteObserverA()
subject.attach(observer1)
const observer2 = new ConcreteObserverB()
subject.attach(observer2)
subject.someBusinessLogic()
subject.someBusinessLogic()
subject.detach(observer2)
subject.someBusinessLogic()
ts
interface Subject {
attach(observer: Observer): void
detach(observer: Observer): void
notify(): void
}
class ConcreteSubject implements Subject {
public state: number
private observers: Observer[] = []
public attach(observer: Observer): void {
const isExist = this.observers.includes(observer)
if (isExist) {
return console.log('Subject: Observer has been attached already.')
}
console.log('Subject: Attached an observer.')
this.observers.push(observer)
}
public detach(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer)
if (observerIndex === -1) {
return console.log('Subject: Nonexistent observer.')
}
this.observers.splice(observerIndex, 1)
console.log('Subject: Detached an observer.')
}
public notify(): void {
console.log('Subject: Notifying observers...')
for (const observer of this.observers) {
observer.update(this)
}
}
public someBusinessLogic(): void {
console.log("\nSubject: I'm doing something important.")
this.state = Math.floor(Math.random() * (10 + 1))
console.log(`Subject: My state has just changed to: ${this.state}`)
this.notify()
}
}
interface Observer {
update(subject: Subject): void
}
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.state < 3) {
console.log('ConcreteObserverA: Reacted to the event.')
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && (subject.state === 0 || subject.state >= 2)) {
console.log('ConcreteObserverB: Reacted to the event.')
}
}
}
const subject = new ConcreteSubject()
const observer1 = new ConcreteObserverA()
subject.attach(observer1)
const observer2 = new ConcreteObserverB()
subject.attach(observer2)
subject.someBusinessLogic()
subject.someBusinessLogic()
subject.detach(observer2)
subject.someBusinessLogic()
状态模式 (State)
能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
适合应用场景:
- 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。将所有特定于状态的代码抽取到一组独立的类中。可以在独立于其他状态的情况下添加新状态或修改已有状态,从而减少维护成本。
- 如果某个类需要根据成员变量的当前值改变自身行为,而需要使用大量的条件语句时,可使用状态模式。将条件语句的分支抽取到相应状态类的方法中。同时,还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。
- 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。能够生成状态类层次结构,通过将公用代码抽取到抽象基类中来减少重复。
优点:
- 单一职责原则。将与特定状态相关的代码放在单独的类中。
- 开闭原则。无需修改已有状态类和上下文就能引入新状态。
- 通过消除臃肿的状态机条件语句简化上下文代码。
缺点:如果状态机只有很少的几个状态,或者很少发生改变,那么应用该模式可能会显得小题大作。
代码示例
js
class Context {
constructor(state) {
this.transitionTo(state)
}
transitionTo(state) {
console.log(`Context: Transition to ${state.constructor.name}.`)
this.state = state
this.state.setContext(this)
}
request1() {
this.state.handle1()
}
request2() {
this.state.handle2()
}
}
class State {
setContext(context) {
this.context = context
}
}
class ConcreteStateA extends State {
handle1() {
console.log('ConcreteStateA handles request1.')
console.log('ConcreteStateA wants to change the state of the context.')
this.context.transitionTo(new ConcreteStateB())
}
handle2() {
console.log('ConcreteStateA handles request2.')
}
}
class ConcreteStateB extends State {
handle1() {
console.log('ConcreteStateB handles request1.')
}
handle2() {
console.log('ConcreteStateB handles request2.')
console.log('ConcreteStateB wants to change the state of the context.')
this.context.transitionTo(new ConcreteStateA())
}
}
const context = new Context(new ConcreteStateA())
context.request1()
context.request2()
ts
class Context {
private state: State
constructor(state: State) {
this.transitionTo(state)
}
public transitionTo(state: State): void {
console.log(`Context: Transition to ${(<any>state).constructor.name}.`)
this.state = state
this.state.setContext(this)
}
public request1(): void {
this.state.handle1()
}
public request2(): void {
this.state.handle2()
}
}
abstract class State {
protected context: Context
public setContext(context: Context) {
this.context = context
}
public abstract handle1(): void
public abstract handle2(): void
}
class ConcreteStateA extends State {
public handle1(): void {
console.log('ConcreteStateA handles request1.')
console.log('ConcreteStateA wants to change the state of the context.')
this.context.transitionTo(new ConcreteStateB())
}
public handle2(): void {
console.log('ConcreteStateA handles request2.')
}
}
class ConcreteStateB extends State {
public handle1(): void {
console.log('ConcreteStateB handles request1.')
}
public handle2(): void {
console.log('ConcreteStateB handles request2.')
console.log('ConcreteStateB wants to change the state of the context.')
this.context.transitionTo(new ConcreteStateA())
}
}
const context = new Context(new ConcreteStateA())
context.request1()
context.request2()
策略模式 (Strategy)
定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。
适合应用场景:
- 使用对象中各种不同的算法变体,并希望能在运行时切换算法时,可使用策略模式。该模式能够将对象关联至可以不同方式执行特定子任务的不同子对象,从而以间接方式在运行时更改对象行为。
- 有仅在执行某些行为时略有不同的相似类时,可使用策略模式。该模式能将不同行为抽取到一个独立类层次结构中,并将原始类组合成同一个,从而减少重复代码。
- 如果算法在上下文的逻辑中不是特别重要,使用策略模式能将类的业务逻辑与其算法实现细节隔离开来。策略模式能将各种算法的代码、内部数据和依赖关系与 其他代码隔离开来。不同客户端可通过一个简单接口执行算法,并能在运行时进行切换。
- 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时,可使用策略模式。策略模式将所有继承自同样接口的算法抽取到独立类中,因此不再需要条件语句。原始对象并不实现所有算法的变体,而是将执行工作委派给其中的一个独立算法对象。
优点:
- 可以在运行时切换对象内的算法。
- 可以将算法的实现和使用算法的代码隔离开来。
- 可以使用组合来代替继承。
- 开闭原则。无需对上下文进行修改就能够引入新的策略。
缺点:
- 如果算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式只会让程序过于复杂。
- 客户端必须知晓策略间的不同 —— 它需要选择合适的策略。
- 许多现代编程语言支持函数类型功能,允许在一组匿名函数中实现不同版本的算法。这样,使用这些函数的方式就和使用策略对象时完全相同,无需借助额外的类和接口来保持代码简洁。
代码示例
js
class Context {
constructor(strategy) {
this.strategy = strategy
}
setStrategy(strategy) {
this.strategy = strategy
}
doSomeBusinessLogic() {
// ...
console.log("Context: Sorting data using the strategy (not sure how it'll do it)")
const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e'])
console.log(result.join(','))
// ...
}
}
class ConcreteStrategyA {
doAlgorithm(data) {
return data.sort()
}
}
class ConcreteStrategyB {
doAlgorithm(data) {
return data.reverse()
}
}
const context = new Context(new ConcreteStrategyA())
console.log('Client: Strategy is set to normal sorting.')
context.doSomeBusinessLogic()
console.log('Client: Strategy is set to reverse sorting.')
context.setStrategy(new ConcreteStrategyB())
context.doSomeBusinessLogic()
ts
class Context {
private strategy: Strategy
constructor(strategy: Strategy) {
this.strategy = strategy
}
public setStrategy(strategy: Strategy) {
this.strategy = strategy
}
public doSomeBusinessLogic(): void {
// ...
console.log("Context: Sorting data using the strategy (not sure how it'll do it)")
const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e'])
console.log(result.join(','))
// ...
}
}
interface Strategy {
doAlgorithm(data: string[]): string[]
}
class ConcreteStrategyA implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.sort()
}
}
class ConcreteStrategyB implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.reverse()
}
}
const context = new Context(new ConcreteStrategyA())
console.log('Client: Strategy is set to normal sorting.')
context.doSomeBusinessLogic()
console.log('Client: Strategy is set to reverse sorting.')
context.setStrategy(new ConcreteStrategyB())
context.doSomeBusinessLogic()
模版方法模式 (Template Method)
在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
适合应用场景:
- 希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可使用模板方法模式。模板方法将整个算法转换为一系列独立的步骤,以便子类能对其进行扩展,同时还可让超类中所定义的结构保持完整。
- 当多个类的算法除一些细微不同之外几乎完全一样时,可使用模版方法模式。但其后果就是,只要算法发生变化,可能需要修改所有的类。在将算法转换为模板方法时,可将相似的实现步骤提取到超类中以去除重复代码。子类间各不同的代码可继续保留在子类中。
优点:
- 可仅允许客户端重写一个大型算法中的特定部分,使得算法其他部分修改对其所造成的影响减小。
- 可将重复代码提取到一个超类中。
缺点:
- 部分客户端可能会受到算法框架的限制。
- 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。
- 模板方法中的步骤越多,其维护工作就可能会越困难。
代码示例
js
class AbstractClass {
templateMethod() {
this.baseOperation1()
this.requiredOperations1()
this.baseOperation2()
this.hook1()
this.requiredOperation2()
this.baseOperation3()
this.hook2()
}
baseOperation1() {
console.log('AbstractClass says: I am doing the bulk of the work')
}
baseOperation2() {
console.log('AbstractClass says: But I let subclasses override some operations')
}
baseOperation3() {
console.log('AbstractClass says: But I am doing the bulk of the work anyway')
}
hook1() {}
hook2() {}
}
class ConcreteClass1 extends AbstractClass {
requiredOperations1() {
console.log('ConcreteClass1 says: Implemented Operation1')
}
requiredOperation2() {
console.log('ConcreteClass1 says: Implemented Operation2')
}
}
class ConcreteClass2 extends AbstractClass {
requiredOperations1() {
console.log('ConcreteClass2 says: Implemented Operation1')
}
requiredOperation2() {
console.log('ConcreteClass2 says: Implemented Operation2')
}
hook1() {
console.log('ConcreteClass2 says: Overridden Hook1')
}
}
function clientCode(abstractClass) {
// ...
abstractClass.templateMethod()
// ...
}
console.log('Same client code can work with different subclasses:')
clientCode(new ConcreteClass1())
console.log('Same client code can work with different subclasses:')
clientCode(new ConcreteClass2())
ts
abstract class AbstractClass {
public templateMethod(): void {
this.baseOperation1()
this.requiredOperations1()
this.baseOperation2()
this.hook1()
this.requiredOperation2()
this.baseOperation3()
this.hook2()
}
protected baseOperation1(): void {
console.log('AbstractClass says: I am doing the bulk of the work')
}
protected baseOperation2(): void {
console.log('AbstractClass says: But I let subclasses override some operations')
}
protected baseOperation3(): void {
console.log('AbstractClass says: But I am doing the bulk of the work anyway')
}
protected abstract requiredOperations1(): void
protected abstract requiredOperation2(): void
protected hook1(): void {}
protected hook2(): void {}
}
class ConcreteClass1 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass1 says: Implemented Operation1')
}
protected requiredOperation2(): void {
console.log('ConcreteClass1 says: Implemented Operation2')
}
}
class ConcreteClass2 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass2 says: Implemented Operation1')
}
protected requiredOperation2(): void {
console.log('ConcreteClass2 says: Implemented Operation2')
}
protected hook1(): void {
console.log('ConcreteClass2 says: Overridden Hook1')
}
}
function clientCode(abstractClass: AbstractClass) {
// ...
abstractClass.templateMethod()
// ...
}
console.log('Same client code can work with different subclasses:')
clientCode(new ConcreteClass1())
console.log('Same client code can work with different subclasses:')
clientCode(new ConcreteClass2())
访问者模式 (Visitor)
将算法与其所作用的对象隔离开来。
适合应用场景:
- 需要对一个复杂对象结构(例如对象树)中的所有元素执行某些操作,可使用访问者模式。访问者模式通过在访问者对象中为多个目标类提供相同操作的变体,能在属于不同类的一组对象上执行同一操作。
- 可使用访问者模式来清理辅助行为的业务逻辑。该模式会将所有非主要的行为抽取到一组访问者类中,使得程序的主要类能更专注于主要的工作。
- 当某个行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义时,可使用该模式。将该行为抽取到单独的访问者类中,只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
优点:
- 开闭原则。可以引入在不同类对象上执行的新行为,且无需对这些类做出修改。
- 单一职责原则。可将同一行为的不同版本移到同一个类中。
- 访问者对象可以在与各种对象交互时收集一些有用的信息。当你想要遍历一些复杂的对象结构(例如对象树),并在结构中的每个对象上应用访问者时,这些信息可能会有所帮助。
缺点:
- 每次在元素层次结构中添加或移除一个类时,都要更新所有的访问者。
- 在访问者同某个元素进行交互时,它们可能没有访问元素私有成员变量和方法的必要权限。
代码示例
js
class ConcreteComponentA {
accept(visitor) {
visitor.visitConcreteComponentA(this)
}
exclusiveMethodOfConcreteComponentA() {
return 'A'
}
}
class ConcreteComponentB {
accept(visitor) {
visitor.visitConcreteComponentB(this)
}
specialMethodOfConcreteComponentB() {
return 'B'
}
}
class ConcreteVisitor1 {
visitConcreteComponentA(element) {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`)
}
visitConcreteComponentB(element) {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`)
}
}
class ConcreteVisitor2 {
visitConcreteComponentA(element) {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`)
}
visitConcreteComponentB(element) {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`)
}
}
function clientCode(components, visitor) {
// ...
for (const component of components) {
component.accept(visitor)
}
// ...
}
const components = [new ConcreteComponentA(), new ConcreteComponentB()]
console.log('The client code works with all visitors via the base Visitor interface:')
const visitor1 = new ConcreteVisitor1()
clientCode(components, visitor1)
console.log('It allows the same client code to work with different types of visitors:')
const visitor2 = new ConcreteVisitor2()
clientCode(components, visitor2)
ts
interface Component {
accept(visitor: Visitor): void
}
class ConcreteComponentA implements Component {
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentA(this)
}
public exclusiveMethodOfConcreteComponentA(): string {
return 'A'
}
}
class ConcreteComponentB implements Component {
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentB(this)
}
public specialMethodOfConcreteComponentB(): string {
return 'B'
}
}
interface Visitor {
visitConcreteComponentA(element: ConcreteComponentA): void
visitConcreteComponentB(element: ConcreteComponentB): void
}
class ConcreteVisitor1 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`)
}
public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`)
}
}
class ConcreteVisitor2 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`)
}
public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`)
}
}
function clientCode(components: Component[], visitor: Visitor) {
// ...
for (const component of components) {
component.accept(visitor)
}
// ...
}
const components = [new ConcreteComponentA(), new ConcreteComponentB()]
console.log('The client code works with all visitors via the base Visitor interface:')
const visitor1 = new ConcreteVisitor1()
clientCode(components, visitor1)
console.log('It allows the same client code to work with different types of visitors:')
const visitor2 = new ConcreteVisitor2()
clientCode(components, visitor2)