木灵鱼儿
阅读:533
结构型模式:装饰器模式
简介
在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。
装饰器模式指:在不改变现有对象结构的情况下,动态的给对象增加一些职责(功能)的一种模式。
这种模式也是防止滥用继承,相对于桥接模式只能桥接两个维度,装饰器模式是可以对接多个维度的,因为他可以嵌套使用。
我们举个例子:
我有一个食物类:class 煎饼
我单点煎饼这个食物是可以的,我也可以加不同的配料:鸡蛋、火腿、豆皮
于是乎我们通过继承得到:
- class 鸡蛋煎饼 extends 煎饼
- class 火腿煎饼 extends 煎饼
- class 豆皮煎饼 extends 煎饼
这样写好像没有什么问题,但是,配料我能不能加两个,我甚至加三个,于是就有了:鸡蛋豆皮煎饼、火腿豆皮煎饼...等等,这个类的数量就会指数上升。
甚至于,我们能不能在鸡蛋豆皮煎饼这个类上再发展出新的子类:加辣、不辣、巨辣
你会发现很简单的需求,就使得类的暴增,这显然不符合我们需求。
解决办法和桥接一样,都是通过使用组合的方式来打断继承。
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。
装饰器模式的角色
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
在简单的需求下,可能就只有一个具体的装饰,那么装饰和抽象就可以作为一个具体装饰来写,省去单独的抽象装饰。
代码实现
//煎饼接口
interface Pancakes {
name(): string;
}
//煎饼的实现
class Pancake implements Pancakes {
name(): string {
return "煎饼";
}
}
//抽象装饰器
class Decorator implements Pancakes {
pancake: Pancake; //聚合
constructor(pancake: Pancake) {
this.pancake = pancake;
}
name(): string {
return this.pancake.name();
}
}
//豆干火腿装饰器
class DGDecorator extends Decorator {
constructor(pancake: Pancake) {
super(pancake);
}
name(): string {
const oldName = super.name();
return `豆干火腿${oldName}`;
}
}
抽象装饰器的作用其实就是为了扩展子类的,如果没有那么多子类扩展,单写一个具体实现也行。
装饰器模式可以理解为类继承的另一种实现,它跟继承一样都会有抽象定义的那些属性方法,完全遵循开闭原则,对原对象修改是关闭的,装饰器来进行扩展。更加灵活。
typescript中的装饰器
由于我们js其实很少用类去编写代码,所以上述代码可能在实际使用中很少去使用,但是我们可以看看ts中装饰器。
由于js的函数存在函数提升,所以目前ts中的装饰器只能用在类上面,因为类是没有提升的,不能未声明前使用。
而js原生装饰器目前还在提案中,ts的装饰器也只是语法糖。
装饰器如果需要再ts中使用,需要配置一下ts.config.ts
{
"compilerOptions": {
"experimentalDecorators": true
}
}
原理
function getName(): string {
return "name";
}
//装饰器函数
function decorator(targetFn: Function) {
return function (...args: any[]) {
if (typeof targetFn !== "function") return;
console.log("装饰器自己的处理");
return targetFn.apply(this, args);
};
}
//调用
const newFn = decorator(getName);
console.log(newFn());
但是这么写的话,如果有6个装饰器,那你的函数就会嵌套多个,非常不利于阅读,所以ts它使用这种方式调用:
@decorator
function getName(): string {
return "name";
}
这是伪代码,因为ts的装饰器他是有自己的对应特殊处理的。
装饰器分类
装饰器分为三种:
- 类装饰器
- 成员装饰器
- 参数装饰器
顾名思义,类装饰器是用于装饰类的,成员装饰器用于装饰类里面的成员,参数则是类里面函数的参数。
类装饰器
function classDecorator(target: Object) {
console.log(target); //class A {}
}
@classDecorator
class A {
name() {
return "A";
}
}
类装饰器也是一个函数,ts只是加了特殊调用的方式@classDecorator
,改函数接受一个参数,这个参数就是被装饰的类本身。
注意:
- void: 当没有返回值的时候,整个类的行为不会被返回值所影响
- new Class: 返回一个新的类的时候,被装饰的类会被这个新的类直接替换(不建议)
成员装饰器
function memberDecorator(target: Object, key: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log(key);
console.log(descriptor);
}
class A {
@memberDecorator
name() {
return "A";
}
}
成员装饰器接受三个参数:
- target:如果装饰器装饰的是静态成员,则target是类本身,如果装饰器装饰的是实例成员,比如上述的name函数,target则是该类的原型prototype对象。
- key:代表当前修饰的键值
- desdescriptor:该key的属性描述对象,可以设置是否允许读写这些
成员装饰器不能有返回值
参数装饰器
function parameterDecorator(target: Object, methodName: string, index: number) {
console.log(target);
console.log(methodName);
console.log(index);
}
class A {
name(@parameterDecorator name: string) {
return name;
}
}
参数装饰器接受三个参数:
- target:如果装饰器装饰的是静态成员方法的参数,则target是类本身,如果装饰器装饰的是实例成员方法的参数,比如上述的name函数,target则是该类的原型prototype对象。
- methodName:装饰的参数所在的方法名称,注意是名称,string的
- index:参数所在函数参数中的索引
参数装饰器不能有返回值
参数装饰器更多的可能是更高阶的用法,比如跟元信息进行配合使用,用来做一些数据校验什么的。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
行为模式:职责链模式
简介责任链模式:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。举个例子,小明因为感冒需要请假,此时它的需要请3天,但是他的小组长只有1天的允许权限,小组长的领导项目经理有2天的权限,而能允许3天的只有老板,但是小明是新员工,所以它只能一步步的问,直到问到老板才成功请到了假期。但是如果小明的公司更大,那么它的审批流程就可能会更加复杂,如果人事流通了,说不定还得重新问一遍,有没有什么办法能方便一点呢?于是就有了职责链模式,它可以理解为一个申请表单,只要有领导在职...

行为模式:策略模式
简介策略模式: 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。简单点来说就是,我现在有10个算法,可能会根据不同的状态使用不同的算法,一般的做法就是if else或者switch这种判断方式,这样的话就跟状态模式一样,后续新增的算法和状态判断又得套一层if判断,这样的话代码的可读性就下降,且维护起来比较麻烦。我们来看个代码例子:function calc(type: number, a: number, b: ...
行为模式:状态模式
简介状态模式:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。简单点来说,我们在平时开发的过程中,会遇到一个对象中会判断一个“状态”的不同,从而会使用不同的行为(函数之类的调用),常见的做法就是if else或者switch这种判断方式,当我们一个状态有多种形式时,可能会产生大量的if else语句,这就不好维护了,如果每次新增一种状态,就得改动一次该对象,显然不符合我们的开闭原则。而状态模式是将每种状态的处理都单独提取出来,然后通过源对象进行组合使用,状态对象会持有源对象,当状态A发生改变时,会调用持有的源对象的设置状态方法,将...

行为模式:解释器模式(太难了,不一定正确)
简介解释器模式:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。因为是自定义的语言,所以它的语法表示肯定是有一些相似的地方,比如必须使用空格分割这种要求,或者使用其他特殊的字符。在这个基础上我们才能进行下一步,而不是说随便写个句子就可以的。举个简单的例子:我们博客编辑文章语言是markdown,而文法就是md中的各种语法,比如## 我是2级标题;这个就表示h2标签,这个规则就是文法,我们需要创建一个解释器来解释语法,说白了就是我们需要将各种文法组成的句子转换成html标签。文法:用于描述语言的语法结构的形式规则解释器会将解释的语言生成一个抽象的语法树,...

行为模式:备忘录模式
简介备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。其实就是一种设计历史记录的方式,在ps中,每次操作的记录都会进行一次记录,用户可以自己选择需要回退到哪条记录,而备忘录模式,是一种能记录一个对象的内部状态,可以通过方法获取到之前保存的内部状态数据,从而进行恢复。这里面最复杂的就是需要设计好需要记录的状态数据,然后通过谁去保存,谁去控制保存的数据,谁去主动进行创建备忘录数据和读取备忘录数据。所以他会有三个角色:备忘录角色:专门存储内部状态数据的类,这个类的实例被管理者管理,被发起者...

行为模式:中介者模式
简介中介者模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。我们来举个例子:机场的飞机降落起飞,都是需要通过塔台进行调度的,如果没有塔台的调度,5架飞机之间需要相互通信确认,以免出现问题,但是如果这么做,一定会出现问题,我们可以看下他们此时的关系网络。每一架飞机都要和其他4架飞机进行沟通通讯,如果这个关系网放在代码上去实现,这是非常难看的,这代码神仙看了都得摇头。而中介者就是为了解决这种网状的关系,通过增加一个中介对象,大家都通过中介对象进行通信,从而将飞机1对4的情况转为1对1的...

行为模式:观察者模式
简介观察者模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。而我们前端vue的响应式原理其实就是观察者模式的实现,只不过他是完全解耦的发布订阅者模式;和基础的观察者模式实现上会有不同,原理都是一样的。观察者模式是为了将重要的部分与辅助部分进行解耦,我们举个例子:在前端中,我们的滚动条距离就可以理解为一个重要部分,我们监听了scroll事件,并在回调函数中处理了一条特殊操作,比如根据滚动的距离操作header元素显隐。此时这个代码量很少,我们知道scroll事件是不建议监听太多的,...
行为模式:迭代器模式
简介迭代器模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。简单来说就是再程序设计中,对于一些自定义的数据结构对象,比如链表啊,或者系统提供的数据结构对象,他们的遍历方法通常会写在数据结构的类中,也就是说数据和遍历的方法是写在同一个类里面。这种方式不利于程序的扩展,因为如果要改动遍历的方法,就必须改动源数据,这违背了开闭原则。既然遍历的方法不能写在数据类中,那么将遍历的方法让客户实现不就好了,但是同样也是不可取的,如果交由客户去写,第一是会暴露很多不必要的属性给客户,第二是增加了客户端的使用负担。而迭代器模式很好的解决了这个问题,在迭代器模式中有以下几个角色...
行为模式:访问者模式
简介访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。有一点点抽象,前一句话的意思是指在写好一个类之后,这个类就基本上不需要改动了(只要需求不改),这其实就是为了解决类结构不变但操作处理逻辑易变的问题,把对数据的操作都封装到访问者类中,我们只需要调用不同的访问者,而无需改变改变结构类,实现了。简单来说就是将一些操作作为扩展分离出去, 这些扩展的功能可能现在没有,以后会有,我们无法知道以后的情况,于是...
行为模式:命令模式
简介命令模式是将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。举个简单的例子:我有一个饭店,共有五个厨师:A,B,C,D,E;每个厨师都会做不同的菜,但是我没有菜单,于是客人就必须知道我有几个厨师,他们分别会做什么菜,于是就会产生下面这些情况:客人1想吃青椒炒蛋,于是问了好几个厨师才知道C厨师会做,于是它通知C厨师做青椒炒蛋客人2想吃冒菜,于是他也问了好几个厨师,找到E厨师制作...更多你会发现客人与厨师耦合度非常之高,这显然不利于饭店的发展,然后我们想一下生活中的真实情况:每个饭店都...