装饰器模式、适配器模式
1.Decorator 装饰器模式
基本原理
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口
- Component Interface:对象的接口(共可装饰类和装饰类使用)
- Component:可装饰的对象
- Decorator:装饰器类,将额外任务应用于组件的类,它还实现了相同的组件接口
interface IComponent {
method(): string
}
//生成装饰器对象的类
class Component implements IComponent {
method(): string {
return 'Component Method'
}
}
//装饰器类
class Decorator implements IComponent {
#object: IComponent
// 接受一个被装饰类
constructor(object: IComponent) {
this.#object = object
}
method(): string {
return `Decorator Method(${this.#object.method()})`
}
}
const COMPONENT = new Component()
console.log(COMPONENT.method()) //Component Method
// Component类被Decorator类装饰
const Decorated = new Decorator(COMPONENT)
console.log(Decorated.method()) //Decorator Method(Component Method)
//被装饰过的类Component可以在被装饰(套娃咯)
const Decorated2 = new Decorator(Decorated)
console.log(Decorated2.method()) //Decorator Method(Decorator Method(Component Method))
应用举例
让我们创建一个名为Value类来保存一个数组,然后添加对数字(Value
)进行加法 (Add
)和减法 (Sub
) 的装饰器来实现给对象添加新的功能,而不改变对象本身的。
Add
和 Sub
装饰器可以直接接受数字、自定义 Value
对象或其他 Add
和 Sub
装饰器
interface IValue {
value: number
}
class Sub implements IValue {
value: number
constructor(val1: IValue | number, val2: IValue | number) {
const left = Object.prototype.hasOwnProperty.call(val1, 'value')
? (val1 as IValue).value
: (val1 as number)
const right = Object.prototype.hasOwnProperty.call(val2, 'value')
? (val2 as IValue).value
: (val2 as number)
this.value = left - right
}
}
class Add implements IValue {
value: number
constructor(val1: IValue | number, val2: IValue | number) {
const left = Object.prototype.hasOwnProperty.call(val1, 'value')
? (val1 as IValue).value
: (val1 as number)
const right = Object.prototype.hasOwnProperty.call(val2, 'value')
? (val2 as IValue).value
: (val2 as number)
this.value = left + right
}
}
//装饰器类
class Value implements IValue {
value: number
constructor(value: number) {
this.value = value
}
}
//装饰器
const A = new Value(1)
const B = new Value(2)
const C = new Value(5)
console.log(new Add(A, B).value) //3
console.log(new Add(A, 100).value) //101
console.log(new Sub(C, A).value) //4
console.log(new Sub(new Add(C, B), A).value) //6
console.log(new Sub(100, 101).value) //-1
console.log(new Add(new Sub(new Add(C, B), A), 100).value)//106
console.log(new Sub(123, Add(C, C)).value)//113
console.log(new Add(new Sub(new Add(C, 10), A), 100).value) //114
console.log(A.value) //1
console.log(B.value) //2
console.log(C.value) //5
2.Adapter适配器模式
一个比较常用构造型设计模式——Adapter适配器模式,关于适配器模式,我们主要需要学习它的两种实现方式,类适配器和对象适配器,以及 5 种常见的应用场景
基本原理
适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适 配器,把两种不兼容的接口,通过转接变得可以一起工作。
比如:你可能有两个相似的类,但它们具有不同的方法签名,因此你在其中一个方法签名之上创建一个适配器,以便更容易在调用时中实现和扩展
适配器模式与前面的装饰器模式有些类似,都是创建完对象后使用,不过设配器模式不能像装饰器模式一样递归调用
- Target: 需要适配的目标接口/类
- Adapter:适配器类
- Adapter Interface: 适配器需要实现的接口,以使目标与客户端兼容(插口和线同)
- Client:调用适配器的应用程序
// Adapter Concept Sample Code
interface IA {
methodA(): void
}
class ClassA implements IA {
methodA() {
console.log('method A')
}
}
interface IB {
methodB(): void
}
class ClassB implements IB {
methodB() {
console.log('method B')
}
}
class ClassBAdapter implements IA {
//ClassB中没有methodA,所以就需要创建适配器来兼容
#classB: ClassB
constructor() {
this.#classB = new ClassB()
}
methodA() {
'calls the class b method_b instead'
this.#classB.methodB()
}
}
const ITEMS = [new ClassA(), new ClassB()]
ITEMS.forEach((item) => {
if (item instanceof ClassB) {
item.methodB()
} else {
item.methodA()
}
})
const ADAPTED = [new ClassA(), new ClassBAdapter()]
ADAPTED.forEach((item) => {
item.methodA()
})
method A
method B
method A
method B
就只是在外部包了一层,因此除了上面的对象适配器还有一种称为类适配器也是同样的效果,类适配器则是采用继承来实现,将将原method继承到复合Target接口的method上,比上面的示例还简单,这里就不写出示例了。
而具体选用那种需要根据实际情况选择
应用举例
假设有一个使用不同工具生产立方体的情形,而不同的工具是由不同的公司发明的,应用程序通过UI界面输入width、height、depth来生产立方体(接口),而输入的这些参数只与A公司的生产工具所需参数相同,而B公司的生产工具的所需参数与A公司并不同(接口也就不同了),那么为了连个接口适配,将B公司的生产工具适配A公司生产工具的接口。
这样在有一个公司(供应商)在忙时就可以使用另一个供应商了
interface ICubeA {
manufacture(width: number, height: number, depth: number): boolean
}
class CubeA implements ICubeA {
static last_time = Date.now()
manufacture(width: number, height: number, depth: number): boolean {
//如果是忙碌中则制作一个正方体
const now = Date.now()
if (now > CubeA.last_time + 1500) {
console.log(
`Company A built Cube with dimensions ${width}x${height}x${depth}`
)
CubeA.last_time = now
return true
}
return false //忙碌中
}
}
//一个假想的B公司的立方体制作工具
interface ICubeB {
create(
top_left_front: [number, number, number],
bottom_right_back: [number, number, number]
): boolean
}
class CubeB implements ICubeB {
static last_time = Date.now()
create(
top_left_front: [number, number, number],
bottom_right_back: [number, number, number]
): boolean {
// 如果不忙,那就用坐标制造一个立方体
const now = Date.now()
if (now > CubeB.last_time + 3000) {
console.log(
`Company B built Cube with coords [${top_left_front[0]},${top_left_front[1]},${top_left_front[2]}],[${bottom_right_back[0]},${bottom_right_back[1]},${bottom_right_back[2]}]`
)
CubeB.last_time = now
return true
} else {
return false // busy
}
}
}
//适配器类,使CubeB类也能使用ICubeA接口
export default class CubeBAdapter implements ICubeA {
#cube: CubeB
constructor() {
this.#cube = new CubeB()
}
manufacture(width: number, height: number, depth: number): boolean {
const success = this.#cube.create(
[0 - width / 2, 0 - height / 2, 0 - depth / 2],
[0 + width / 2, 0 + height / 2, 0 + depth / 2]
)
return success
}
}
const totalCubes = 5
let counter = 0
const manufactureCube = () => {
const width = Math.floor(Math.random() * 10) + 1
const height = Math.floor(Math.random() * 10) + 1
const depth = Math.floor(Math.random() * 10) + 1
let cube = new CubeA()
let success = cube.manufacture(width, height, depth)
if (success) {
counter = counter + 1
} else {
// try other manufacturer
console.log('Company A was busy, so trying company B')
cube = new CubeBAdapter()
success = cube.manufacture(width, height, depth)
if (success) {
counter = counter + 1
} else {
console.log('Company B was busy, so trying company A')
}
}
}
// wait some time between manufacturing each cube
const interval = setInterval(() => {
manufactureCube()
if (counter >= totalCubes) {
clearInterval(interval)
console.log(`${totalCubes} cubes have been manufactured`)
}
}, 1000)
Company A was busy, so trying company B
Company B was busy, so trying company A
Company A built Cube with dimensions 6x5x10
Company A was busy, so trying company B
Company B built Cube with coords [-4,-3,-2.5],[4,3,2.5]
Company A built Cube with dimensions 4x5x3
Company A was busy, so trying company B
Company B was busy, so trying company A
Company A built Cube with dimensions 10x2x1
Company A was busy, so trying company B
Company B built Cube with coords [-0.5,-2,-2.5],[0.5,2,2.5]
5 cubes have been manufactured