关于装饰器:浅谈python装饰器的实现原理

一、装璜器在初学装璜器的时候,会感觉形象生涩,其实装璜器就是对某个对象进行性能上的加强上面具体讲一下 “加强” 的实现原理 二、加强的实现原理其实原理就一句话:更改旧对象的指向,指向 结构的新的函数wrapper def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper@logdef now(): print('2015-3-25')>>> now()call now():2015-3-25下面是拿廖雪峰-python-装璜器中的例子,在此做一个具体阐明:a. python解释器在执行now()之前,首先加载环境,加载内存中对应的程序 b. 找到后,自上而下执行,于是先执行 @log即执行: now = log(now) 咱们来具体看一下log(now)产生了什么,重点关注指向对象的变动1、首先解释器依据变量名log,找到内存中对应的程序2、now函数传入log函数,重点来了,参数func和now 此时指向同一个对象,即内存中存储的```def now(): print('2015-3-25')```3、此时解释器持续向下读取程序,直到遇到return返回。这里要留神,此时调用的是log函数,log外部的wrapper函数(闭包)此时并没有被调用,所以此时执行log函数的return语句`return wrapper`log函数将`wrapper`返回c. log(now)返回wrapper,而后赋值给now,重点来了,now变量指向的对象从 def now(): print('2015-3-25')变为 def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw)d. abc完结后,解释器执行完@log,接下来读取到now()语句,开始执行: 1. 此时now曾经和wrapper指向同一个对象,这里多说一句,其实此时如果运行 ``` >>> now.__name__ 'wrapper' ```如果想要防止这种状况,能够应用@functools.wraps(func)2. 咱们继续执行,遇到`print('call %s():' % func.__name__)`,执行print内容3. 遇到`return func(*args, **kw)`,还记得吗,下面咱们提到func指向的对象是谁?4. 调用func5. 解释器找到func指向的程序, ``` def now(): print('2015-3-25') ```6. 执行 `print('2015-3-25')`e. 至此,now()执行完结,依据abcd的阐明,大家应该能够了解返回的后果了 ...

January 19, 2022 · 1 min · jiezi

关于装饰器:设计模式之装饰器模式

基本概念装璜器模式,顾名思义起的是装璜的作用,就是在一个类上减少性能。如果通过继承来减少性能,在不批改代码的状况下,如果减少性能多的话,会使类的数量爆炸式增长,为治理带来微小的麻烦。装璜器模式就比拟好地解决了这一点。 介绍以下为装璜器模式的通用类图:  Component,个别是接口或者抽象类,定义了最简略的办法,装璜器类和被装璜类都要实现该接口。ConcreteComponent,被装璜类,实现了Component。Decorator,装璜器类,通过该类为ConcreteComponent动静增加额定的办法,实现了Component接口,并且该对象中持有一个Component的成员变量。ConcreteDecoratorA,ConcreteDecoratorB,具体的装璜类,该类中的办法就是要为ConcreteComponent动静增加的办法。实现咱们以生产一件衣服为例,生产一件衣服自身是个很简略的过程,一块布料裁剪好了之后做出衣服的样子就能够了,然而这样的衣服是卖不出去的,因为毫无美感,咱们须要通过一些装璜来使衣服变得难看。然而时代在变动,人们的审美也在变动,装璜总是一直在变的,所以咱们就要有一个灵活机动的模式来批改装璜。 Clothes.java public interface Clothes {     public void makeClothes(); } MakeClothes.javapublic class MakeClothes implements Clothes {      @Override     public void makeClothes() {         System.out.println("制作一件衣服");     }  } 步骤 3 创立装璜器。OperationSubstract.javapublic class OperationSubstract implements Strategy{    @Override    public int doOperation(int num1, int num2) {       return num1 - num2;    } } 话不多说,先来个衣服的最后成品,就是毫无美感的那种,那么如果当初要减少装璜,能够用一个类继承MakeClothes,而后减少外面makeClothes()办法,然而如果过几天装璜就变了,那么又要改变代码,而且如果装璜过多,这个类就显得很庞杂,不好保护,这个时候装璜器模式就来大显神通了。 Decorator.javapublic class Decorator implements Clothes {      private Clothes clothes;     public Decorator(Clothes _clothes) {         this.clothes = _clothes;     }     @Override     public void makeClothes() {         clothes.makeClothes();     } } 这就是一个装璜器,它有一个构造函数,参数是一个衣服类,同时它重写了makeClothes()办法,以便它的子类对其进行批改。上面是两个子类,别离对衣服进行了绣花和镂空 Embroidery.javapublic class Embroidery extends Decorator {      public Embroidery(Clothes _clothes) {         super(_clothes);     }     public void embroidery() {         System.out.println("给衣服绣花");     }     @Override     public void makeClothes() {         super.makeClothes();         this.embroidery();     } } Hollow.javapublic class Hollow extends Decorator {      public Hollow(Clothes _clothes) {         super(_clothes);     }     public void hollow() {         System.out.println("要害地位镂空");     }     @Override     public void makeClothes() {         super.makeClothes();         this.hollow();     } } 这两个子类的结构器都传入一个衣服模型,而且两个子类别离有各自的办法——绣花和镂空,然而他们均重写了makeClothes()办法,在制作衣服的过程中退出了绣花和镂空的操作,这样一来,咱们只须要增删改这几个装璜器的子类,就能够实现各种不同的装璜,简洁明了,高深莫测。上面测试一下: DecoratorDemo.javapublic class DecoratorDemo {      public static void main(String[] args) {         Clothes clothes = new MakeClothes();         clothes = new Embroidery(clothes);         clothes = new Hollow(clothes);         clothes.makeClothes();         System.out.println("衣服做好了");     } } 执行程序,输入后果: 制作一件衣服 给衣服绣花 要害地位镂空 衣服做好了

September 18, 2020 · 1 min · jiezi

TypeScript 注解(下)

引言继上周的文章《TypeScript 注解(上)》,补充在最后未实现的属性注解。本文可能需要扎实的JavaScript功底,我尽量给大家通俗地讲解。实现建立新项目上周的项目建立的太不正规了,直接建个文件夹,然后写的TS代码,太过不规范。这次我们使用npm init初始化一个新的前台项目。创建成功后,项目中就出现了我们熟知的package.json文件。安装依赖本次,我们需要使用反射实现一些高级的功能。反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。根据官方文档的环境要求,使用TypeScript反射,我们需要先安装reflect-metadata包。npm i reflect-metadata –save属性注解属性注解在属性上声明,属性注解的表达式在运行期间像函数一样被调用,并带着以下两个参数:如果是在静态成员上,则是类的构造函数;如是在实例成员上,则是类的原型。成员的名称。所以,一个标准的属性注解如下所示:返回一个带有两个参数的闭包。function annotation(target: string) { return function(target, propertyKey) { }}看下面的实例代码:这里直接返回Reflect.metadata代替原闭包,因为metadata方法的返回值,就符合该规范。import “reflect-metadata”;// 声明唯一的元数据keyconst formatMetaDataKey = Symbol(“format”);// 前缀注解function prefix(target: string) { // 根据key将前缀存至元数据中 return Reflect.metadata(formatMetaDataKey, target);}class Person { constructor(message: string) { this.message = message; } @prefix(“hello, “) message: string; speak(): void { // 获取元数据前缀,key,当前对象,message属性 const prefix = Reflect.getMetadata(formatMetaDataKey, this, “message”); console.log(prefix + this.message); }}const person = new Person(“world”);person.speak();通过在字符串属性上声明的prefix注解,来添加该字符串应该显示的前缀。功能很简单,也就不再赘述了。以后想写类似功能时照葫芦画瓢就是了,这里主要是对一些比较繁琐的代码加以研究。Symbol大家想必注意到了这里的Symbol了,这是个什么东西呢?// 声明唯一的元数据keyconst formatMetaDataKey = Symbol(“format”);Symbol是一种新的基本数据类型。去Google搜索JavaScript数据类型,发现谷歌的第一条已然过时,面试也常考,这里纠正一下。JavaScript共有7种数据类型,重点!!!6种基本数据类型:BooleanNullUndefinedNumberStringSymbol (ECMAScript 6 新定义)和Object。例子说来说去,Symbol到底是个啥玩意?一个例子带你看明白。let obj = { id: 1};obj[‘id’] = 2;// 覆盖了原idconsole.log(obj);const id = Symbol(‘id’);obj[id] = 3;// 添加Symbol key的属性console.log(obj);const id2 = Symbol(‘id’);obj[id2] = 6;// 不覆盖原Symbol keyconsole.log(obj);对象中允许字符串或Symbol作为key,从这个例子我们可以看出,字符串的key不具有唯一性,而Symbol的key能保证唯一,不进行覆盖。Token这个唯一性覆盖问题就像之前在网上送检项目中遇到的问题,因为项目已停工,也就没有和大家提。这里和大家简单说一下。使用的全局配置是使用字符串注入的,因为是一个容器,假设我们的框架中也用到了’CONFIG’的字符串注入,那我们加入的配置就会覆盖原框架配置,给框架注入了错误的对象,然后就出现了错误但是就是找不着原因的一系列问题。providers: [ { provide: ‘CONFIG’, useValue: YUNZHI_GLOBAL_CONFIG }]class TestComponent { constructor(@Inject(‘CONFIG’) private config) { }}其实Angular已经解决,以后不要再通过字符串注入,推荐通过InjectionToken注入。const CONFIG_TOKEN = new InjectionToken<VALUE_TYPE>(‘CONFIG’);总结初级程序员的业余生活,学习,学习,学习。 ...

April 19, 2019 · 1 min · jiezi

Golang 函数执行时间统计装饰器的一个实现

背景最近在搭一个新项目的架子,在生产环境中,为了能实时的监控程序的运行状态,少不了逻辑执行时间长度的统计。时间统计这个功能实现的期望有下面几点:实现细节要剥离:时间统计实现的细节不期望在显式的写在主逻辑中。因为主逻辑中的其他逻辑和时间统计的抽象层次不在同一个层级用于时间统计的代码可复用统计出来的时间结果是可被处理的。对并发编程友好实现思路统计细节的剥离最朴素的时间统计的实现,可能是下面这个样子:func f() { startTime := time.Now() logicStepOne() logicStepTwo() endTime := time.Now() timeDiff := timeDiff(startTime, endTime) log.Info(“time diff: %s”, timeDiff)}《代码整洁之道》告诉我们:一个函数里面的所有函数调用都应该处于同一个抽象层级。在这里时间开始、结束的获取,使用时间的求差,属于时间统计的细节,首先他不属于主流程必要的一步,其次他们使用的函数 time.Now() 和 logicStepOne, logicStepTwo 并不在同一个抽象层级。因此比较好的做法应该是把时间统计放在函数 f 的上层,比如:func doFWithTimeRecord() { startTime: = time.Now() f() endTime := Time.Now() timeDiff := timeDIff(startTime, endTime) log.Info(“time diff: %s”, timeDiff)}时间统计代码可复用&统计结果可被处理&不影响原函数的使用方式我们虽然达成了函数内抽象层级相同的目标,但是大家肯定也能感受到:这个函数并不好用。原因在于,我们把要调用的函数 f 写死在了 doFWithTimeRecord 函数中。这意味着,每一个要统计时间的函数,我都需要实现一个 doXXWithTimeRecord, 而这些函数里面的逻辑是相同的,这就违反了我们 DRY(Don’t Repeat Yourself)原则。因此为了实现逻辑的复用,我认为装饰器是比较好的实现方式:将要执行的函数作为参数传入到时间统计函数中。举个网上看到的例子实现一个功能,第一反应肯定是查找同行有没有现成的轮子。不过看了下,没有达到自己的期望,举个例子:type SumFunc func(int64, int64) int64func timedSumFunc(f SumFunc) SumFunc { return func(start, end int64) int64 { defer func(t time.Time) { fmt.Printf("— Time Elapsed: %v —\n", time.Since(t)) }(time.Now()) return f(start, end) }}说说这段代码不好的地方:这个装饰器入参写死了函数的类型:type SumFunc func(int64, int64) int64也就是说,只要换一个函数,这个装饰器就不能用了,这不符合我们的第2点要求这里时间统计结果直接打印到了标准输出,也就是说这个结果是不能被原函数的调用方去使用的:因为只有掉用方,才知道这个结果符不符合预期,是花太多时间了,还是正常现象。这不符合我们的第3点要求。怎么解决这两个问题呢?这个时候,《重构,改善既有代码的设计》告诉我们:Replace Method with Method Obejct——以函数对象取代函数。他的意思是当一个函数有比较复杂的临时变量时,我们可以考虑将函数封装成一个类。这样我们的函数就统一成了 0 个参数。(当然,原本就是作为一个 struct 里面的方法的话就适当做调整就好了)现在,我们的代码变成了这样:type TimeRecorder interface { SetCost(time.Duration) TimeCost() time.Duration}func TimeCostDecorator(rec TimeRecorder, f func()) func() { return func() { startTime := time.Now() f() endTime := time.Now() timeCost := endTime.Sub(startTime) rec.SetCost(timeCost) }}这里入参写成是一个 interface ,目的是允许各种函数对象入参,只需要实现了 SetCost 和 TimeCost 方法即可对并发编程友好最后需要考虑的一个问题,很多时候,一个类在整个程序的生命周期是一个单例,这样在 SetCost 的时候,就需要考虑并发写的问题。这里考虑一下几种解决方案:使用装饰器配套的时间统计存储对象,实现如下:func NewTimeRecorder() TimeRecorder { return &timeRecorder{}}type timeRecorder struct { cost time.Duration}func (tr *timeRecorder) SetCost(cost time.Duration) { tr.cost = cost}func (tr *timeRecorder) Cost() time.Duration { return tr.cost}抽离出存粹的执行完就可以销毁的函数对象,每次要操作的时候都 new 一下函数对象内部对 SetCost 函数实现锁机制这三个方案是按推荐指数从高到低排序的,因为我个人认为:资源允许的情况下,尽量保持对象不可变;同时怎么统计、存储使用时长其实是统计时间模块自己的事情。单元测试最后补上单元测试:func TestTimeCostDecorator(t *testing.T) { testFunc := func() { time.Sleep(time.Duration(1) * time.Second) } type args struct { rec TimeRecorder f func() } tests := []struct { name string args args }{ { “test time cost decorator”, args{ NewTimeRecorder(), testFunc, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := TimeCostDecorator(tt.args.rec, tt.args.f) got() if tt.args.rec.Cost().Round(time.Second) != time.Duration(1) * time.Second.Round(time.Second) { “Record time cost abnormal, recorded cost: %s, real cost: %s”, tt.args.rec.Cost().String(), tt.Duration(1) * time.Second, } }) }}测试通过,验证了时间统计是没问题的。至此,这个时间统计装饰器就介绍完了。如果这个实现有什么问题,或者大家有更好的实现方式,欢迎大家批评指正与提出~原文地址:https://blog.coordinate35.cn/… ...

March 18, 2019 · 2 min · jiezi

前端常用设计模式(1)--装饰器(decorator)

一.引子-先来安利一款好游戏《塞尔达传说-荒野之息》,这款于2017年3月3日由任天堂(“民间高手”)发售在自家主机平台WIIU和SWITCH上的单机RPG游戏,可谓是跨时代的“神作”了。第一次制作“开放类”游戏的任天堂就教科书般的定义了这类游戏应该如何制作。而这个游戏真正吸引我的地方是他的细节,举个栗子,《荒野之息》中的世界有天气和温度两个概念,会下雨打雷,有严寒酷暑,但是这些天气不想大多数游戏一样,只是简单的背景,而是实实在在会影响主角林克(Link)每一个操作。比如,下雨天去爬山会打滑;打雷天如果身上有金属装备会被雷劈(木制装备则没事!);严寒中会慢慢流失体力(穿上一件保暖衣就解决了);酷暑中使用爆炸箭则会原地爆炸!等等;就是这些细节让这个游戏世界显的无比真实又有趣。二.问题-如何设计这样的游戏代码?作为程序猿,玩游戏之余不禁会思考,这样的游戏代码应该如何设计编写?比如“攀爬”这个动作,需要判断攀爬的位置,林克的装备(有些装备能让你爬的更快),当时的天气,林克的体力等等众多条件,里面肯定参杂的无数if else,更何况这只是其中一个简单的操作,拓展到全部游戏,其复杂的不可想象。显然这样的设计是不行的。那我们假设“攀爬”的方法只专心处理攀爬这件事(有体力就能成功,反之失败),其他判断在方法外部执行,比如判断天气,装备,位置等等,这样就符合了程序设计的单一职责和低耦合等原则,并且判断天气的方法还可以拿去别的地方复用,增强了代码的复用度和可测试度,似乎可行!那应该如何设计这样的代码呢?这就引出了我们今天的主角-装饰器模式。三.主角-装饰器模式(decorator)根据GoF在《设计模式:可复用面向对象软件的基础》(以下简称《设计模式》)一书中对装饰器模式定义:装饰器模式又称包装模式(“wrapper”),目的是以对用户透明的方式扩展对象的功能,是继承的一种代替方案。一起划重点:对用户透明:一般指被装饰过的对象的对外接口不变,“攀爬”被怎么装饰都还是“攀爬”。扩展对象的功能:一般指修改或添加对象功能,比如林克在雪地就可以用盾牌滑雪,平地则没有这个能力。继承的一种代替方案:熟悉面向对象的同学一定对继承并不陌生,这里我们重点谈谈继承本身的一些缺点:1)继承中子类和超类存在强耦合性,超类的修改会影响全部子类;2)超类对子类是“白盒复用”,子类必须了解超类的全部实现,破坏了封装性。3)当项目庞大时,继承会使得子类爆发性增长,比如《荒野之息》中存在料理系统,任意两种食材均可以搭配出一款料理,假定有10中可以使用食材,使用继承的方式就要构建10*10=100个子类表示料理结果,而装饰器模式仅仅使用10+1=11个子类就可以完成以上工作。(还包括了任意种食材的混合,事实上游戏中的确可以。)最后,总结一下装饰器模式的特点:不改变对象自身的基础上,在程序运行时给对象添加某种功能,一句话:锦上添花。(想想《王者荣耀》中最赚钱的皮肤,怎么全是游戏,喂!)四.场景-面向切片编程(AOP)说到装饰器,最经典的应用场景就是面向切片编程(Aspect Oriented Programming,以下简称AOP),AOP适合某些具有横向逻辑(可切片)的应用,比如提交表单,点击提交按钮以后执行的逻辑是:上报点击 -> 校验数据 -> 提交数据 -> 上报结果 。可以看到,首尾的上报日志功能和核心业务逻辑并没有直接关系,并且几乎所有表单提交都需要上报日志的功能,因此,上报日志,这个功能就可以单独抽象出来,最后在程序运行(或编译)时动态织入业务逻辑中。类似的功能还有:数据校验,权限控制,异常处理,缓存管理等等。AOP的优点是可以保持业务逻辑模块的纯净和高内聚,同时方便功能复用,通过装饰器就可以很方便的把功能模块装饰到主业务逻辑中去。五.应用-前端开发中的应用接下来我们一起看看具体装饰器模式是如何在前端开发中应用的。Talk is cheap, show me the code! (屁话少说,放码过来!)在JS中改变一个对象再简单不过了。得力于JS是一门基于原型的弱类型语言,给对象添加或修改功能都十分容易,因此传统的面向对象中的装饰器模式在JS中的应用并不太多(ES6正式提出class以后场景有所增加)。我们先简单模拟一下面向对象中的装饰器模式。假设我们要开发一个飞机大战的游戏,飞机可以切换装备的武器,发射不同的子弹。我们先实现一个飞机的类,并实现一个fire方法。接着,我们实现一个发射导弹的装饰器类这个类接收一个飞机实例,并且重新实现了fire方法,在方法内部先调用原来实例的fire方法,接着扩展此方法,增加了发射导弹的功能。类似的我们再实现一个发射原子弹的装饰器。最后我们看一下应该如何使用这两个装饰器。可以看到,经过两个装饰器装饰后的plane实例,再调用fire方法时,就可以同时发射三种子弹了。而装饰器本身并没有直接改写Plane类,只是增强了它的fire方法,对plane实例的使用者也是透明的。接下来我们看一看如何应用装饰器在JS中实现AOP编程。首先我们扩展一下函数的原型,让每个函数都可以被装饰。我们给函数增加一个before和after方法,这两个方法各自接收一个新的函数,并保证新函数在原函数之前(before)或之后(after)执行。这里需要注意的是新函数和原函数具有相同this和参数。有了两个方法,以前很多复杂的需求就变得很简单了。栗子一:挂载多个onload函数通常情况下,window.onload只能挂载一个回调函数,重复声明回调函数,后面的会把之前声明的覆盖掉,有了after以后,这个麻烦解决了。栗子二:日志上报栗子三:追加(改变)参数比如,为了增加安全性,给所有接口都增加一个token参数,如果不实用AOP,我们只能改ajax方法了。但是有了AOP,就可以像下面这样操作。原理就是before函数和原函数接收相同的this和参数,并且before会在原函数之前执行。其实AOP在前端项目中的应用场景还很多,比如校验表单参数,异常处理,数据缓存,本地持久化等,这里不在一一举例了。有些同学对直接改写函数的原型比较抵触,这里我们也给出函数式的before实现。六.ES7-@decorator语法在JS未来的标准(ES7)中,装饰器也已被加入到了提案中。前端同学都知道jQuery最大的特点就是它链式调用的API设计,其核心是每个方法都返回this,也就是jQuery对象实例,我们不妨先实现一个高阶函数,用于实现链式调用。fluent函数接收一个函数fn作为参数,返回一个新的函数,在新函数内部通过apply调用fn,并最终返回上下文this。有了这个函数,我们就可以很方便的给任意对象的方法添加链式调用。接下来,我们看看如何使用ES7的@decorator语法来简化上面的代码,先来看一下结果。熟悉JAVA的同学一眼就看出这不是注解写法么,没错,ES7中的@decorator正是参考了Python和JAVA语法设计出来的。@后面的fluentDecorate是一个装饰器函数,这个函数接收三个参数,分别是target,name和descriptor,这三个参数和Object.defineProperty方法的参数完全相同,实际上@decorator也正是这个方法的语法糖而已。值得注意的是@decorator不止可以作用在对象或类的方法上面,还可以直接作用在类(class)上,区别是装饰函数的第一个参数target不同,当作用在方法上时,target指向对象本身,而当作用在类时target指向类(class),并且name和descriptor都是undefined。以下给出fluentDecorate函数的完整实现。通常我们可以把这个装饰函数再抽象一下,让他成为一个高阶函数,可以接收我们最开始定义的fluent函数或者其他函数(比如截流函数等),然后返回一个用这个函数装饰的新装饰函数,更具有通用型。@decorator到目前为止还只是个提案,没有任何浏览器支持了这个语法,但是好在可以使用Babel以插件(transform-decorators-legacy)的形式在自己的项目中体验。注意,@decorator只能作用于类和类的方法上,不能用于普通函数,因为函数存在变量提升,而类是不会提升的。七.组件-装饰器在React项目中的应用最后结合目前前端最火的框架React,来看看装饰器是如何在组件上使用的。回到最开始的假设,如何开发出《荒野之息》这样细节丰富的游戏,下面我们就使用React搭配装饰器来模拟一下游戏中的细节实现。我们先实现一个Person组件,用来代指游戏的主角,这个组件可以接收名字,生命值,攻击类等初始化参数,并在一个卡片中展示这些参数,当生命值为0时,会提示“游戏结束”。并且在卡片中放置一个“JUMP”按钮,用点击按钮模拟主角跳跃的交互。组件调用:实现结果如下,是不是很抽象?哈哈!接下来我们想要模拟游戏中的天气和温度变化,需要实现一个“自然环境”的组件Natural,这个组件自身有天气(wat)和温度(tep)两个状态(state),并且可以通过输入改变这两个状态,我们之前创建的Person组件作为后代插入这个组件中,并且接收Natural的wat和tep状态作为属性。好了,我们的实验页面就完成了,最终效果如下,上面可以通过进度条和单选按钮改变天气和温度,改变后的结果通过props传递给游戏主角。但是现在改变温度和天气对主角并不会造成任何影响,接下来我们想在不改变原有Person组件的前提下,实现两个功能:第一,当温度大于50度或者小于10度的时候,主角生命值慢慢下降;第二当天气是雨天的时候,主角每跳跃3次就失败1次。先来实现第一个功能,温度过高和过低时,主角生命值慢慢减少。我们的思路是实现一个装饰器,用这个装饰器在外部装饰Person组件,使得这个组件可以感知温度变化。先给出实现:仔细观察decorateTep函数,它接收一个组件(A)作为参数,返回一个新的React组件(B),在B内部维护了一个hp和tep状态 ,在tep处于临界值时,改变B的hp,最后render时用B的hp代替原来的hp属性传递给A组件。这不是就是高阶组件(HOC)么?!没错,当装饰器去装饰一个组件时,它的实现和高阶组件完全一致。通过返回一个新组件的方式去增强原有组件的能力,这也符合React提倡的组件组合的设计模式(注意不是mixin或者继承),decorateTep的使用方法很简单,一行代码搞定:接下来我们来实现第二个功能,下雨时跳跃会偶尔失败,这里我们换一个策略,不再装饰Person组件,而是装饰组件内部的onJump跳跃方法。代码如下:区别之前的decorateTep,这个decorateWat装饰器的重点是第三个参数descriptor,之前提到,descriptor参数是被装饰方法的描述对象,它的value属性指向的就是原方法(onJump),这里我们用变量method保存原方法,同时使用i记录点击次数,通过闭包延长这两个变量的生命周期,最后实现一个新的方法代替原方法,在新方法内部通过apply调用原方法并重置变量i,注意decorateWat最后返回的是改变以后的descriptor对象。经过装饰器装饰过的onJump方法如下:好了,接下来就是见证奇迹的时刻!八.轮子-常用装饰器库事实上现在已经有很多开源装饰器的库可以拿来使用,以下是质量较好的轮子,希望可以给大家提供帮助。core-decoratorslodash-decoratorsreact-decoration九.参考-相关资料阅读全部演示源代码五分钟让你明白为什么塞尔达可以夺得年度游戏《荒野之息》中46个精彩的小细节日亚上一位玩家对《荒野之息》的评价面向切片编程《JavaScript 设计模式与开发实践》曾探;人民邮电出版社《JavaScript 高级程序设计(第三版)》Zakas;人民邮电出版社《ES 6 标准入门(第二版)》阮一峰;电子工业出版社最后,如有不对的地方,欢迎各位小伙伴留言拍砖,你们的支持是我继续的最大动力!谢谢大家!

February 25, 2019 · 1 min · jiezi

Python装饰器高级用法

在Python中,装饰器一般用来修饰函数,实现公共功能,达到代码复用的目的。在函数定义前加上@xxxx,然后函数就注入了某些行为,很神奇!然而,这只是语法糖而已。原文地址:https://python-book.readthedocs.io微信公众号:小菜学编程 (coding-fan)场景假设,有一些工作函数,用来对数据做不同的处理:def work_bar(data): passdef work_foo(data): pass我们想在函数调用前/后输出日志,怎么办?<!–more–>傻瓜解法logging.info(‘begin call work_bar’)work_bar(1)logging.info(‘call work_bar done’)如果有多处代码调用呢?想想就怕!函数包装傻瓜解法无非是有太多代码冗余,每次函数调用都要写一遍logging。可以把这部分冗余逻辑封装到一个新函数里:def smart_work_bar(data): logging.info(‘begin call: work_bar’) work_bar(data) logging.info(‘call doen: work_bar’)这样,每次调用smart_work_bar即可:smart_work_bar(1)# …smart_work_bar(some_data)通用闭包看上去挺完美……然而,当work_foo也有同样的需要时,还要再实现一遍smart_work_foo吗?这样显然不科学呀!别急,我们可以用闭包:def log_call(func): def proxy(*args, **kwargs): logging.info(‘begin call: {name}’.format(name=func.func_name)) result = func(*args, **kwargs) logging.info(‘call done: {name}’.format(name=func.func_name)) return result return proxy这个函数接收一个函数对象(被代理函数)作为参数,返回一个代理函数。调用代理函数时,先输出日志,然后调用被代理函数,调用完成后再输出日志,最后返回调用结果。这样,不就达到通用化的目的了吗?——对于任意被代理函数func,log_call均可轻松应对。smart_work_bar = log_call(work_bar)smart_work_foo = log_call(work_foo)smart_work_bar(1)smart_work_foo(1)# …smart_work_bar(some_data)smart_work_foo(some_data)第1行中,log_call接收参数work_bar,返回一个代理函数proxy,并赋给smart_work_bar。第4行中,调用smart_work_bar,也就是代理函数proxy,先输出日志,然后调用func也就是work_bar,最后再输出日志。注意到,代理函数中,func与传进去的work_bar对象紧紧关联在一起了,这就是闭包。再提一下,可以覆盖被代理函数名,以smart_为前缀取新名字还是显得有些累赘:work_bar = log_call(work_bar)work_foo = log_call(work_foo)work_bar(1)work_foo(1)语法糖先来看看以下代码:def work_bar(data): passwork_bar = log_call(work_bar)def work_foo(data): passwork_foo = log_call(work_foo)虽然代码没有什么冗余了,但是看是去还是不够直观。这时候,语法糖来了~~~@log_calldef work_bar(data): pass因此,注意一点(划重点啦),这里@log_call的作用只是:告诉Python编译器插入代码work_bar = log_call(work_bar)。求值装饰器先来猜猜装饰器eval_now有什么作用?def eval_now(func): return func()看上去好奇怪哦,没有定义代理函数,算装饰器吗?@eval_nowdef foo(): return 1print foo这段代码输出1,也就是对函数进行调用求值。那么到底有什么用呢?直接写foo = 1不行么?在这个简单的例子,这么写当然可以啦。来看一个更复杂的例子——初始化一个日志对象:# some other code before…# log formatformatter = logging.Formatter( ‘[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s’, ‘%Y-%m-%d %H:%M:%S’,)# stdout handlerstdout_handler = logging.StreamHandler(sys.stdout)stdout_handler.setFormatter(formatter)stdout_handler.setLevel(logging.DEBUG)# stderr handlerstderr_handler = logging.StreamHandler(sys.stderr)stderr_handler.setFormatter(formatter)stderr_handler.setLevel(logging.ERROR)# logger objectlogger = logging.Logger(name)logger.setLevel(logging.DEBUG)logger.addHandler(stdout_handler)logger.addHandler(stderr_handler)# again some other code after…用eval_now的方式:# some other code before…@eval_nowdef logger(): # log format formatter = logging.Formatter( ‘[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s’, ‘%Y-%m-%d %H:%M:%S’, ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(name) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) return logger# again some other code after…两段代码要达到的目的是一样的,但是后者显然更清晰,颇有代码块的风范。更重要的是,函数调用在局部名字空间完成初始化,避免临时变量(如formatter等)污染外部的名字空间(比如全局)。带参数装饰器定义一个装饰器,用于记录慢函数调用:def log_slow_call(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > 1: logging.warn(‘slow call: {name} in {seconds}s’.format( name=func.func_name, seconds=seconds, )) return result return proxy第3、5行分别在函数调用前后采样当前时间,第7行计算调用耗时,耗时大于一秒输出一条警告日志。@log_slow_calldef sleep_seconds(seconds): time.sleep(seconds)sleep_seconds(0.1) # 没有日志输出sleep_seconds(2) # 输出警告日志然而,阈值设置总是要视情况决定,不同的函数可能会设置不同的值。如果阈值有办法参数化就好了:def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn(‘slow call: {name} in {seconds}s’.format( name=func.func_name, seconds=seconds, )) return result return proxy然而,@xxxx语法糖总是以被装饰函数为参数调用装饰器,也就是说没有机会传递threshold参数。怎么办呢?——用一个闭包封装threshold参数:def log_slow_call(threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn(‘slow call: {name} in {seconds}s’.format( name=func.func_name, seconds=seconds, )) return result return proxy return decorator@log_slow_call(threshold=0.5)def sleep_seconds(seconds): time.sleep(seconds)这样,log_slow_call(threshold=0.5)调用返回函数decorator,函数拥有闭包变量threshold,值为0.5。decorator再装饰sleep_seconds。采用默认阈值,函数调用还是不能省略:@log_slow_call()def sleep_seconds(seconds): time.sleep(seconds)处女座可能会对第一行这对括号感到不爽,那么可以这样改进:def log_slow_call(func=None, threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn(‘slow call: {name} in {seconds}s’.format( name=func.func_name, seconds=seconds, )) return result return proxy if func is None: return decorator else: return decorator(func)这种写法兼容两种不同的用法,用法A默认阈值(无调用);用法B自定义阈值(有调用)。# Case A@log_slow_calldef sleep_seconds(seconds): time.sleep(seconds)# Case B@log_slow_call(threshold=0.5)def sleep_seconds(seconds): time.sleep(seconds)用法A中,发生的事情是log_slow_call(sleep_seconds),也就是func参数是非空的,这是直接调decorator进行包装并返回(阈值是默认的)。用法B中,先发生的是log_slow_call(threshold=0.5),func参数为空,直接返回新的装饰器decorator,关联闭包变量threshold,值为0.5;然后,decorator再装饰函数sleep_seconds,即decorator(sleep_seconds)。注意到,此时threshold关联的值是0.5,完成定制化。你可能注意到了,这里最好使用关键字参数这种调用方式——使用位置参数会很丑陋:# Case B-@log_slow_call(None, 0.5)def sleep_seconds(seconds): time.sleep(seconds)当然了,函数调用尽量使用关键字参数是一种极佳实践,含义清晰,在参数很多的情况下更是如此。智能装饰器上节介绍的写法,嵌套层次较多,如果每个类似的装饰器都用这种方法实现,还是比较费劲的(脑子不够用),也比较容易出错。假设有一个智能装饰器smart_decorator,修饰装饰器log_slow_call,便可获得同样的能力。这样,log_slow_call定义将变得更清晰,实现起来也更省力啦:@smart_decoratordef log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn(‘slow call: {name} in {seconds}s’.format( name=func.func_name, seconds=seconds, )) return result return proxy脑洞开完,smart_decorator如何实现呢?其实也简单:def smart_decorator(decorator): def decorator_proxy(func=None, **kwargs): if func is not None: return decorator(func=func, **kwargs) def decorator_proxy(func): return decorator(func=func, **kwargs) return decorator_proxy return decorator_proxysmart_decorator实现了以后,设想就成立了!这时,log_slow_call,就是decorator_proxy(外层),关联的闭包变量decorator是本节最开始定义的log_slow_call(为了避免歧义,称为real_log_slow_call)。log_slow_call支持以下各种用法:# Case A@log_slow_calldef sleep_seconds(seconds): time.sleep(seconds)用法A中,执行的是decorator_proxy(sleep_seconds)(外层),func非空,kwargs为空;直接执行decorator(func=func, **kwargs),即real_log_slow_call(sleep_seconds),结果是关联默认参数的proxy。# Case B# Same to Case A@log_slow_call()def sleep_seconds(seconds): time.sleep(seconds)用法B中,先执行decorator_proxy(),func及kwargs均为空,返回decorator_proxy对象(内层);再执行decorator_proxy(sleep_seconds)(内层);最后执行decorator(func, **kwargs),等价于real_log_slow_call(sleep_seconds),效果与用法A一致。# Case C@log_slow_call(threshold=0.5)def sleep_seconds(seconds): time.sleep(seconds)用法C中,先执行decorator_proxy(threshold=0.5),func为空但kwargs非空,返回decorator_proxy对象(内层);再执行decorator_proxy(sleep_seconds)(内层);最后执行decorator(sleep_seconds, **kwargs),等价于real_log_slow_call(sleep_seconds, threshold=0.5),阈值实现自定义!订阅更新,获取更多学习资料,请关注我们的 微信公众号 : ...

February 24, 2019 · 2 min · jiezi

Vue框架TypeScript装饰器使用指南

关键词 装饰器 Decorator 元编程前言装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。本篇先从项目的宏观角度来总结一下Decorator如何组织。我会持续分享一些知识整理,如果文章对您有帮助记得点赞鼓励一下哦????~,也可以通过邮件方式联系我文章列表: https://juejin.im/user/5bc8b9… 邮箱地址: 595506910@qq.com目录主要的Decorator依赖vue-class-componentvuex-classvue-property-decoratorcore-decorators自定义Decorator示例哪些功能适合用Decorator实现Decorator实现小TipsSee also主要的Decorator依赖vue-cli3 默认支持Decorator, 年初重写了一个design库主要依赖官方和社区提供的Decorator来实现的组件。Decorator可以非侵入的装饰类、方法、属性,解耦业务逻辑和辅助功能逻辑。以下是主要的三方Decorator组件,有了这些组件常用的Vue特性就可以全部转成Decorator风格了。vue-class-component@Component 如果您在声明组件时更喜欢基于类的 API,则可以使用官方维护的 vue-class-component 装饰器实时计算computed属性, get computedMsg () {return ‘computed ’ + this.msg}生命周期钩子 mounted () {this.greet()}vuex-class让Vuex和Vue之间的绑定更清晰和可拓展@State@Getter@Action@Mutationvue-property-decorator这个组件完全依赖于vue-class-component.它具备以下几个属性:@Component (完全继承于vue-class-component)@Prop:父子组件之间值的传递@Emit:子组件向父组件传递@Model:双向绑定@Watch:观察的表达式变动@Provice:在组件嵌套层级过深时。父组件不便于向子组件传递数据。就把数据通过Provide传递下去。@Inject:然后子组件通过Inject来获取Mixins (在vue-class-component中定义);建议项目中只引用vue-property-decorator就可以了,避免@Component从vue-class-component和vue-property-decorator两个中随意引用。core-decorators@readonly@autobind : TSX 回调函数中的 this,类的方法默认是不会绑定 this 的,可以使用autobind装饰器@override总结一下主要就分成这三类:修饰类的:@Component、@autobind;修饰方法的:@Emit、@Watch、@readonly、@override;修饰属性的:@Prop、@readonly;以上引用方法等详系内容可查看官方文档。要想完整的发挥Decorator的价值就需要根据需要自定义一些装饰器。下面自定义部分就来实现一个记录日志功能的装饰器。自定义Decorator示例@Logger,1.Logger日志装饰器通常是修饰方法,Decorater则是在运行时就被触发了,日志记录是在方法被调用时触发,示例中通过自动发布事件实现调用时触发。2.为增加日志记录的灵活性,需要通过暴露钩子函数的方式来改变日志记录的内容。期望的日志格式{ “logId”:"", // 事件Id “input”:"", // 方法输入的内容 “output”:"", // 方法输出的内容 “custom”:"" // 自定义的日志内容}实现export function Logger(logId?: string, hander?: Function) { const loggerInfo =Object.seal({logId:logId, input:’’,output:’’, custom: ‘’}); const channelName = ‘__logger’; const msgChannel = postal.channel(channelName); msgChannel.subscribe(logId, logData => { // 根据业务逻辑来处理日志 console.log(logData); }); return function (target: any, key: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> { const oldValue = descriptor.value descriptor.value = function () { const args: Array<any> = []; for (let index in arguments) { args.push(arguments[index]); } loggerInfo.input = ${key}(${args.join(',')}); // 执行原方法 const value = oldValue.apply(this, arguments); loggerInfo.output = value; hander && (loggerInfo.custom = hander(loggerInfo.input, loggerInfo.output) || ‘’); // 被调用时,会自动发出一个事件 msgChannel.publish(logId, loggerInfo); } return descriptor }}使用// 直接使用非常简洁@Logger(’event_get_detial1’)getDetial(id?: string, category?: string) { return “详细内容”;}// 或者使用自定义,让日志和业务逻辑分离@Logger(’event_get_detial2’, (input, output) => { return ‘我是自定义内容’;})getDetial2(id?: string, category?: string) { return “详细内容”;}…<button @click=“getDetial2(‘1000’, ‘a’)">获取详情</button>效果: {logId: “event_get_detial2”, input: “getDetial2(1000,a)”, output: “详细内容”, custom: “我是自定义内容”}, 每次点击按钮都会触发一次。TODO: 这里还需要对输入参数和输出参数中的引用数据类型做处理。同时还需要掌握:装饰器工厂、装饰器组合、装饰器求值、参数装饰器、元数据哪些功能适合用Decorator实现官网和社区提供的这些Decorator, 可以作为自己框架的底层设计。日志功能全局都得用,调用方法基本一致,是最适合使用装饰器来实现,并且每个项目的日志记录各有差异,最适合自定义这部分。Decorator实现小Tips考虑下各类Decorator叠加和共存的问题,可以参考官网关于装饰器组合描述Decorator 的目标是在原有功能基础上,添加功能,切忌覆盖原有功能类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的类)装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。类是不会提升的,所以就没有这方面的问题。注意迁移速度、避免一口吃成胖子的做法不要另起炉灶对主流库创建Decorator库,主流库维护成本很高还是得有官方来维护,为保证质量不使用个人编写的Decorator库。自己在创建Decorator库时也要有这个意识,仅做一些有必要自定义的。Decorator 不是管道模式,decorator之间不存在交互,所以必须注意保持decorator独立性、透明性Decorator 更适用于非业务功能需求确定 decorator 的用途后,切记执行判断参数类型decorator 针对每个装饰目标,仅执行一次See alsots官网装饰器说明【深入阅读】 https://www.tslang.cn/docs/ha...Decorator作用在不同的声明类型target、key说明 https://segmentfault.com/a/11...JS 装饰器(Decorator)场景实战 https://zhuanlan.zhihu.com/p/...Decorators in ES7 https://zhuanlan.zhihu.com/p/...vue组件 风格指南 https://cn.vuejs.org/v2/style…使用Typescript封装一款装饰器风格的Web框架 https://zhuanlan.zhihu.com/p/...Axios 装饰器实现 1.https://github.com/glangzh/re… 2.https://www.npmjs.com/package…声明计算属性用的。 https://my.oschina.net/lpcysz...autobind https://blog.csdn.net/liubigg...vue-class-component源码阅读 https://www.jianshu.com/p/cfe…函数和方法的区别 https://www.imooc.com/article…很多人整理过使用方法 https://www.cnblogs.com/navys…举个例子 join(separator?: string): string;vue源码解析系列-compute实现机制 https://segmentfault.com/a/11… ...

February 20, 2019 · 1 min · jiezi

装饰器与元数据反射(3)参数装饰器

之前已经分别介绍了方法装饰器、属性装饰器和类装饰器,这篇文章我们来继续关注这些话题:参数装饰器装饰器工厂我们将围绕以下这个例子,来探讨这些概念:class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } public saySomething(something : string) : string { return this.name + " " + this.surname + " says: " + something; }}参数装饰器TypeScript对于参数装饰器的声明如下declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;如下我们为类Person的saySomething方法的参数添加一个参数装饰器public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; }最终被编译为JavaScript的样子为:Object.defineProperty(Person.prototype, “saySomething”, __decorate( [__param(0, logParameter)], Person.prototype, “saySomething”, Object.getOwnPropertyDescriptor(Person.prototype, “saySomething”) ));return Person;如果将其和之前的装饰器比较,是否会发现又使用了Object.defineProperty()方法,那么是否意味着saySomething将被__decorated函数的返回值替换?我们发现这里有个新函数__param,TypeScript编译器生成如下:var __param = this.__param || function (index, decorator) { // 返回一个装饰器函数 return function (target, key) { // 应用装饰器(忽略返回值) decorator(target, key, index); }};如上所示,调用参数装饰器,其并没有返回值,这就意味着,函数__decorate的调用返回并没有覆盖方法saySomething,也很好理解:参数装饰器要毛返回。可见参数装饰器函数需要3个参数:被装饰类的原型,装饰参数所属的方法名,参数的索引。具体的实现如下:function logParameter(target: any, key : string, index : number) { var metadataKey = log_${key}_parameters; if (Array.isArray(target[metadataKey])) { target[metadataKey].push(index); } else { target[metadataKey] = [index]; }}其中向类的原型中增加一个新的属性metadataKey,该属性值是一个数组,包含所装饰参数的索引,可以把它当作元数据。参数装饰器不应当用来修改构造器、方法或属性的行为,它只应当用来产生某种元数据。一旦元数据被创建,我们便可以用其它的装饰器去读取它。装饰器工厂官方TypeScript装饰器建议定义一个如下的装饰器工厂:装饰器工厂首先是一个函数,它接受任意数量的参数,同时返回如前所述的四种之一特定类型的装饰器。虽然已经讨论四种装饰是如何实现及使用的,但还是有一些可以改进的地方,观察下面的代码片段:@logClassclass Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @logMethod public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; }}这里装饰器的使用是没问题的,但如果我们可以不关心装饰器的类型,而在任何地方使用岂不方便,就像下面的样子:@logclass Person { @log public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @log public saySomething(@log something : string) : string { return this.name + " " + this.surname + " says: " + something; }}这边是装饰器工厂的使用诉求,它可以识别具体情况下该使用哪种类型的装饰器,幸运的是,我们可以通过传递给装饰器的参数来区分它的类型。function log(…args : any[]) { switch(args.length) { case 1: return logClass.apply(this, args); case 2: return logProperty.apply(this, args); case 3: if(typeof args[2] === “number”) { return logParameter.apply(this, args); } return logMethod.apply(this, args); default: throw new Error(“Decorators are not valid here!”); }} ...

February 2, 2019 · 2 min · jiezi

装饰器与元数据反射(2)属与类性装饰器

上一篇文章中,我们讨论了TypeScript源码中关于方法装饰器的实现,搞明白了如下几个问题:装饰器函数是如何被调用的?装饰器函数参数是如何传入的?__decorate函数干了些什么事情?接下来我们继续属性装饰器的观察。属性装饰器属性装饰器的声明标识如下:declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;如下我们为一个类的属性添加了一个名为@logProperty的装饰器class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; }}上一篇解释过,当这段代码最后被编译成JavaScript执行时,方法__decorate会被调用,但此处会少最后一个参数(通过Object. getOwnPropertyDescriptor属性描述符)var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } __decorate( [logProperty], Person.prototype, “name” ); return Person;})();需要注意的是,这次TypeScript编译器并没像方法装饰器那样,使用__decorate返回的结果覆盖原始属性。原因是属性装饰器并不需要返回什么。Object.defineProperty(C.prototype, “foo”, __decorate( [log], C.prototype, “foo”, Object.getOwnPropertyDescriptor(C.prototype, “foo”) ));那么,接下来具体实现这个@logProperty装饰器function logProperty(target: any, key: string) { // 属性值 var _val = this[key]; // getter var getter = function () { console.log(Get: ${key} =&gt; ${_val}); return _val; }; // setter var setter = function (newVal) { console.log(Set: ${key} =&gt; ${newVal}); _val = newVal; }; // 删除属性 if (delete this[key]) { // 创建新的属性 Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }); }}实现过程首先声明了一个变量_val,并用所装饰的属性值给它赋值(此处的this指向类的原型,key为属性的名字)。接着声明了两个方法getter和setter,由于函数是闭包创建的,所以在其中可以访问变量_val,在其中可以添加额外的自定义行为,这里添加了将属性值打印在控制台的操作。然后使用delete操作符将原属性从类的原型中删除,不过需要注意的是:如果属性存在不可配置的属性时,这里if(delete this[key])会返回false。而当属性被成功删除,方法Object.defineProperty()将创建一个和原属性同名的属性,不同的是新的属性getter和setter方法,使用上面新创建的。至此,属性装饰器的实现就完成了,运行结果如下:var me = new Person(“Remo”, “Jansen”); // Set: name => Remome.name = “Remo H.”; // Set: name => Remo H.name;// Get: name Remo H.类装饰器类装饰器的声明标识如下:declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;可以像如下方式使用类装饰器:@logClassclass Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; }}和之前不同的是,经过TypeScript编译器编译为JavaScript后,调用__decorate函数时,与方法装饰器相比少了后两个参数。仅传递了Person而非Person.prototype。var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } Person = __decorate( [logClass], Person ); return Person;})();值得注意的是,__decorate的返回值复写了原始的构造函数,原因是类装饰器必须返回一个构造器函数。接下来我们就来实现上面用到的类装饰器@logClass:function logClass(target: any) { // 保存对原始构造函数的引用 var original = target; // 用来生成类实例的方法 function construct(constructor, args) { var c : any = function () { return constructor.apply(this, args); } c.prototype = constructor.prototype; return new c(); } // 新的构造函数 var f : any = function (…args) { console.log(“New: " + original.name); return construct(original, args); } // 复制原型以便intanceof操作符可以使用 f.prototype = original.prototype; // 返回新的构造函数(会覆盖原有构造函数) return f;}这里实现的构造器中,声明了一个名为original的变量,并将所装饰类的构造函数赋值给它。接着声明一个工具函数construct,用来创建类的实例。然后定义新的构造函数f,在其中调用原来的构造函数并将初始化的类名打印在控制台,当然我们也可以添加一些其他自定义的行为。原始构造函数的原型被复制给f的原型,以确保在创建一个Person的新实例时,instanceof操作符如愿以偿,具体原因可参考鄙人另一篇文章原型与对象。至此类装饰器的实现就完成了,可以验证下:var me = new Person(“Remo”, “Jansen”); // New: Personme instanceof Person; // true ...

February 2, 2019 · 2 min · jiezi

装饰器与元数据反射(1)方法装饰器

让我来深入地了解一下TypeScript对于装饰器模式的实现,以及反射与依赖注入等相关特性。在Typescript的源代码中,可以看到装饰器能用来修饰class,property,method,parameter:declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;接下来深入地了解一下每种装饰器:方法装饰器首先来根据上面的标识,实现一个名为log的方法装饰器。使用装饰器的方法很简单:在装饰器名前加@字符,写在想要装饰的方法上,类似写注释的方式:class C { @log foo(n: number) { return n * 2; }}装饰器实际上是一个函数,入参为所装饰的方法,返回值为装饰后的方法。在使用之前需要提前实现这个装饰器函数,如下:function log(target: Function, key: string, descriptor: any) { // target === C.prototype // key === “foo” // descriptor === Object.getOwnPropertyDescriptor(C.prototype, “foo”) // 保存对原方法的引用,避免重写 var originalMethod = descriptor.value; descriptor.value = function (…args: any[]) { // 将“foo”函数的参数列表转化为字符串 var a = args.map(a => JSON.stringify(a)).join(); // 调用 foo() 并获取它的返回值 var result = originalMethod.apply(this, args); // 将返回的结果转成字符串 var r = JSON.stringify(result); // 打印日志 console.log(Call: ${key}(${a}) =&gt; ${r}); // 返回调用 foo 的结果 return result; } // 返回已编辑的描述符 return descriptor;}该装饰器函数包含三个参数:target:所要修饰的方法。key:被修饰方法的名字。descriptor:属性描述符,如果为给定可以通过调用Object.getOwnPropertyDescriptor()来获取。我们观察到,类C中使用的装饰器函数log并没有显式的参数传递,不免好奇它所需要的参数是如何传递的?以及该函数是如何被调用的?TypeScript最终还是会被编译为JavaScript执行,为了搞清上面的问题,我们来看一下TypeScript编译器将类C的定义最终生成的JavaScript代码:var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; Object.defineProperty(C.prototype, “foo”, __decorate([ log ], C.prototype, “foo”, Object.getOwnPropertyDescriptor(C.prototype, “foo”))); return C;})();而为添加装饰器所生成的JavaScript代码如下:var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; return C;})();对比两者发现使用装饰的不同,只是在类定义中,多了如下代码:Object.defineProperty( __decorate( [log], // 装饰器 C.prototype, // target:C的原型 “foo”, // key:装饰器修饰的方法名 Object.getOwnPropertyDescriptor(C.prototype, “foo”) // descriptor ););通过查询MDN文档,可以知悉defineProperty的作用:Object.defineProperty()方法可直接在一个对象上定义一个新的属性,或者修改对象上一个已有的属性,然后返回这个对象。TypeScript编译器通过defineProperty方法重写了所修饰的方法foo,新方法的实现是由函数__decorate返回的,那么问题来了:__decorate函数在哪声明的呢?掘地三尺不难找到,来一起把玩一下:var __decorate = this.__decorate || function (decorators, target, key, desc) { if (typeof Reflect === “object” && typeof Reflect.decorate === “function”) { return Reflect.decorate(decorators, target, key, desc); } switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); }};第一行使用了或操作符(||),以确保如果函数__decorate已被创建,他将不会被重写。if (typeof Reflect === “object” && typeof Reflect.decorate === “function”)第二行是一个条件语句,使用了JavaScript的一个新特性:元数据反射。这个主题后续再展开讲述,下面我们先聚焦观察下该新特性的兼容方案:switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc);}此处__decorate函数接受了4个参数,所以case 4将被执行。平心而论这块代码有点生涩,没关系掰开揉碎了看。reduceRight方法接受一个函数作为累加器和数组的每个值(从右到左)将其减少为单个值。为了方便理解,上面的代码重写如下:[log].reduceRight(function(log, desc) { if(log) { return log(C.prototype, “foo”, desc); } else { return desc; }}, Object.getOwnPropertyDescriptor(C.prototype, “foo”));可以看到当这段代码执行的时候,装饰器函数log被调用,并且参数C.prototype,“foo”,previousValue也被传入,如此之前的问题现在可以解答了。经过装饰过的foo方法,它依然按照原来的方式执行,只是额外执行了附件的装饰器函数log的功能。const c = new C();const r = c.foo(23); // “Call: foo(23) => 46"console.log(r); // 46 ...

February 1, 2019 · 2 min · jiezi

python 装饰器 part2

python 装饰器传参被装饰的函数带有参数的情况接上一篇,直接上代码import timedef decorator(func): def process(*args, **kwargs): start = time.time() func(*args, **kwargs) end = time.time() print(‘函数func(也就是被装饰的函数)的运行时间是:{}’.format(end-start)) return process@decorator # python 装饰器的正确使用,不需要传参def decorated(): time.sleep() print(‘decorated function’)@decorator # python 装饰器的正确使用,需要传参 def decorated(key, val): time.sleep() print(‘decorated function’)# 此时不用再像上面一样赋值,可以直接调用decorated()decorated(key, val)</python>返回值被装饰的函数有返回值在装饰器内部需return被装饰函数的调用代码:import timedef decorator(func): def process(*args, **kwargs): start = time.time() return func(*args, **kwargs) # end = time.time() # print(‘函数func(也就是被装饰的函数)的运行时间是:{}’.format(end-start)) return process@decorator # python 装饰器的正确使用,不需要传参def decorated(): time.sleep() print(‘decorated function’) return ‘来自不带参数的被装饰函数’@decorator # python 装饰器的正确使用,需要传参 def decorated(key, val): time.sleep() print(‘decorated function’) return ‘来自带有参数的被装饰函数’# 此时不用再像上面一样赋值,可以直接调用decorated()decorated(key, val)装饰器带参数@decorator(val=’’)需要对装饰期代码再包装一层代码import timedef warpper(val_type): def decorator(func): def process(*args, **kwargs): start = time.time() return func(*args, **kwargs) return process return decorator@decorator(val_type=’’) # python 装饰器的正确使用,不需要传参def decorated(): time.sleep() print(‘decorated function’) return ‘来自不带参数的被装饰函数’@decorator(val_type=’’) # python 装饰器的正确使用,需要传参 def decorated(key, val): time.sleep() print(‘decorated function’) return ‘来自带有参数的被装饰函数’# 此时不用再像上面一样赋值,可以直接调用decorated()decorated(key, val) ...

January 24, 2019 · 1 min · jiezi

PHP设计模式范例 — DesignPatternsPHP(2)结构型设计模式

【搬运于GitHub开源项目DesignPatternsPHP】项目地址:戳我2、结构型设计模式在软件工程中,结构型设计模式集是用来抽象真实程序中的对象实体之间的关系,并使这种关系可被描述,概括和具体化。2.1 适配器模式2.1.1 目的将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。2.1.2 例子数据库客户端库适配器使用不同的webservices,通过适配器来标准化输出数据,从而保证不同webservice输出的数据是一致的2.1.3 UML图2.1.4 代码你可以在 GitHub 上找到这些代码BookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface BookInterface{ public function turnPage(); public function open(); public function getPage(): int;}Book.php<?phpnamespace DesignPatterns\Structural\Adapter;class Book implements BookInterface{ /** * @var int / private $page; public function open() { $this->page = 1; } public function turnPage() { $this->page++; } public function getPage(): int { return $this->page; }}EBookAdapter.php<?phpnamespace DesignPatterns\Structural\Adapter;/* * This is the adapter here. Notice it implements BookInterface, * therefore you don’t have to change the code of the client which is using a Book /class EBookAdapter implements BookInterface{ /* * @var EBookInterface / protected $eBook; /* * @param EBookInterface $eBook / public function __construct(EBookInterface $eBook) { $this->eBook = $eBook; } /* * This class makes the proper translation from one interface to another. / public function open() { $this->eBook->unlock(); } public function turnPage() { $this->eBook->pressNext(); } /* * notice the adapted behavior here: EBookInterface::getPage() will return two integers, but BookInterface * supports only a current page getter, so we adapt the behavior here * * @return int / public function getPage(): int { return $this->eBook->getPage()[0]; }}EBookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface EBookInterface{ public function unlock(); public function pressNext(); /* * returns current page and total number of pages, like [10, 100] is page 10 of 100 * * @return int[] / public function getPage(): array;}Kindle.php<?phpnamespace DesignPatterns\Structural\Adapter;/* * this is the adapted class. In production code, this could be a class from another package, some vendor code. * Notice that it uses another naming scheme and the implementation does something similar but in another way /class Kindle implements EBookInterface{ /* * @var int / private $page = 1; /* * @var int / private $totalPages = 100; public function pressNext() { $this->page++; } public function unlock() { } /* * returns current page and total number of pages, like [10, 100] is page 10 of 100 * * @return int[] / public function getPage(): array { return [$this->page, $this->totalPages]; }}2.2 桥接模式2.2.1 目的解耦一个对象的实现与抽象,这样两者可以独立地变化。2.2.2 例子Symfony DoctrineBridge2.2.3 UML图2.2.4 代码你可以在 GitHub 上找到这些代码Formatter.php<?phpnamespace DesignPatterns\Structural\Bridge;interface Formatter{ public function format(string $text): string;}PlainTextFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class PlainTextFormatter implements Formatter{ public function format(string $text): string { return $text; }}HtmlFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class HtmlFormatter implements Formatter{ public function format(string $text): string { return sprintf(’<p>%s</p>’, $text); }}Service.php<?phpnamespace DesignPatterns\Structural\Bridge;abstract class Service{ /* * @var Formatter / protected $implementation; /* * @param Formatter $printer / public function __construct(Formatter $printer) { $this->implementation = $printer; } /* * @param Formatter $printer / public function setImplementation(Formatter $printer) { $this->implementation = $printer; } abstract public function get(): string;}HelloWorldService.php<?phpnamespace DesignPatterns\Structural\Bridge;class HelloWorldService extends Service{ public function get(): string { return $this->implementation->format(‘Hello World’); }}PingService.php<?phpnamespace DesignPatterns\Structural\Bridge;class PingService extends Service{ public function get(): string { return $this->implementation->format(‘pong’); }}2.3 组合模式2.3.1 目的以单个对象的方式来对待一组对象2.3.2 例子form类的实例包含多个子元素,而它也像单个子元素那样响应render()请求,当调用render()方法时,它会历遍所有的子元素,调用render()方法Zend_Config: 配置选项树, 其每一个分支都是Zend_Config对象2.3.3 UML图2.3.4 代码你可以在 GitHub 上找到这些代码RenderableInterface.php<?phpnamespace DesignPatterns\Structural\Composite;interface RenderableInterface{ public function render(): string;}Form.php<?phpnamespace DesignPatterns\Structural\Composite;/* * The composite node MUST extend the component contract. This is mandatory for building * a tree of components. /class Form implements RenderableInterface{ /* * @var RenderableInterface[] / private $elements; /* * runs through all elements and calls render() on them, then returns the complete representation * of the form. * * from the outside, one will not see this and the form will act like a single object instance * * @return string / public function render(): string { $formCode = ‘<form>’; foreach ($this->elements as $element) { $formCode .= $element->render(); } $formCode .= ‘</form>’; return $formCode; } /* * @param RenderableInterface $element / public function addElement(RenderableInterface $element) { $this->elements[] = $element; }}InputElement.php<?phpnamespace DesignPatterns\Structural\Composite;class InputElement implements RenderableInterface{ public function render(): string { return ‘<input type=“text” />’; }}TextElement.php<?phpnamespace DesignPatterns\Structural\Composite;class TextElement implements RenderableInterface{ /* * @var string / private $text; public function __construct(string $text) { $this->text = $text; } public function render(): string { return $this->text; }}2.4 数据映射器2.4.1 目的数据映射器是一个数据访问层,用于将数据在持久性数据存储(通常是一个关系数据库)和内存中的数据表示(领域层)之间进行相互转换。其目的是为了将数据的内存表示、持久存储、数据访问进行分离。该层由一个或者多个映射器组成(或者数据访问对象),并且进行数据的转换。映射器的实现在范围上有所不同。通用映射器将处理许多不同领域的实体类型,而专用映射器将处理一个或几个。此模式的主要特点是,与Active Record不同,其数据模式遵循单一职责原则(Single Responsibility Principle)。2.4.2 例子DB对象关系映射器(ORM): Doctrine2使用“EntityRepository”作为DAO2.4.3 UML图2.4.4 代码你可以在 GitHub 上找到这些代码User.php<?phpnamespace DesignPatterns\Structural\DataMapper;class User{ /* * @var string / private $username; /* * @var string / private $email; public static function fromState(array $state): User { // validate state before accessing keys! return new self( $state[‘username’], $state[’email’] ); } public function __construct(string $username, string $email) { // validate parameters before setting them! $this->username = $username; $this->email = $email; } /* * @return string / public function getUsername() { return $this->username; } /* * @return string / public function getEmail() { return $this->email; }}UserMapper.php<?phpnamespace DesignPatterns\Structural\DataMapper;class UserMapper{ /* * @var StorageAdapter / private $adapter; /* * @param StorageAdapter $storage / public function __construct(StorageAdapter $storage) { $this->adapter = $storage; } /* * finds a user from storage based on ID and returns a User object located * in memory. Normally this kind of logic will be implemented using the Repository pattern. * However the important part is in mapRowToUser() below, that will create a business object from the * data fetched from storage * * @param int $id * * @return User / public function findById(int $id): User { $result = $this->adapter->find($id); if ($result === null) { throw new \InvalidArgumentException(“User #$id not found”); } return $this->mapRowToUser($result); } private function mapRowToUser(array $row): User { return User::fromState($row); }}StorageAdapter.php<?phpnamespace DesignPatterns\Structural\DataMapper;class StorageAdapter{ /* * @var array / private $data = []; public function __construct(array $data) { $this->data = $data; } /* * @param int $id * * @return array|null / public function find(int $id) { if (isset($this->data[$id])) { return $this->data[$id]; } return null; }}2.5 装饰器2.5.1 目的动态地为类的实例添加功能2.5.2 例子Zend Framework: Zend_Form_Element 实例的装饰器Web Service层:REST服务的JSON与XML装饰器(当然,在此只能使用其中的一种)2.5.3 UML图2.5.4 代码你可以在 GitHub 上找到这些代码Booking.php<?phpnamespace DesignPatterns\Structural\Decorator;interface Booking{ public function calculatePrice(): int; public function getDescription(): string;}BookingDecorator.php<?phpnamespace DesignPatterns\Structural\Decorator;abstract class BookingDecorator implements Booking{ /* * @var Booking / protected $booking; public function __construct(Booking $booking) { $this->booking = $booking; }}DoubleRoomBooking.php<?phpnamespace DesignPatterns\Structural\Decorator;class DoubleRoomBooking implements Booking{ public function calculatePrice(): int { return 40; } public function getDescription(): string { return ‘double room’; }}ExtraBed.php<?phpnamespace DesignPatterns\Structural\Decorator;class ExtraBed extends BookingDecorator{ private const PRICE = 30; public function calculatePrice(): int { return $this->booking->calculatePrice() + self::PRICE; } public function getDescription(): string { return $this->booking->getDescription() . ’ with extra bed’; }}WiFi.php<?phpnamespace DesignPatterns\Structural\Decorator;class WiFi extends BookingDecorator{ private const PRICE = 2; public function calculatePrice(): int { return $this->booking->calculatePrice() + self::PRICE; } public function getDescription(): string { return $this->booking->getDescription() . ’ with wifi’; }}2.6 依赖注入2.6.1 目的实现了松耦合的软件架构,可得到更好的测试,管理和扩展的代码2.6.2 用例注入DatabaseConfiguration, DatabaseConnection将从$config获得所需的所有内容。没有DI(依赖注入),配置将直接在DatabaseConnection中创建,这不利于测试和扩展它。2.6.3 例子Doctrine2 ORM 使用了依赖注入,它通过配置注入了 Connection 对象。为了达到方便测试的目的,可以很容易的通过配置创建一个mock的 Connection 对象。Symfony 和 Zend Framework 2 也有了专门的依赖注入容器,用来通过配置数据创建需要的对象(比如在控制器中使用依赖注入容器获取所需的对象)2.6.4 UML图2.6.5 代码你可以在 GitHub 上找到这些代码DatabaseConfiguration.php<?phpnamespace DesignPatterns\Structural\DependencyInjection;class DatabaseConfiguration{ /* * @var string / private $host; /* * @var int / private $port; /* * @var string / private $username; /* * @var string / private $password; public function __construct(string $host, int $port, string $username, string $password) { $this->host = $host; $this->port = $port; $this->username = $username; $this->password = $password; } public function getHost(): string { return $this->host; } public function getPort(): int { return $this->port; } public function getUsername(): string { return $this->username; } public function getPassword(): string { return $this->password; }}DatabaseConnection.php<?phpnamespace DesignPatterns\Structural\DependencyInjection;class DatabaseConnection{ /* * @var DatabaseConfiguration / private $configuration; /* * @param DatabaseConfiguration $config / public function __construct(DatabaseConfiguration $config) { $this->configuration = $config; } public function getDsn(): string { // this is just for the sake of demonstration, not a real DSN // notice that only the injected config is used here, so there is // a real separation of concerns here return sprintf( ‘%s:%s@%s:%d’, $this->configuration->getUsername(), $this->configuration->getPassword(), $this->configuration->getHost(), $this->configuration->getPort() ); }}2.7 外观模式2.7.1 目的Facade模式的主要目标不是避免您必须阅读复杂API的手册。这只是副作用。主要目的是减少耦合并遵循Demeter定律。Facade通过嵌入多个(当然,有时只有一个)接口来解耦访客与子系统,当然也降低复杂度。Facade不会禁止你访问子系统你可以为一个子系统提供多个 Facade因此一个好的 Facade 里面不会有 new 。如果每个方法里都要构造多个对象,那么它就不是 Facade,而是生成器或者 [ 抽象 | 静态 | 简单 ] 工厂方法。优秀的 Facade 不会有 new,并且构造函数参数是接口类型的。如果你需要创建一个新实例,则在参数中传入一个工厂对象。2.7.2 UML图2.7.3 代码你可以在 GitHub 上找到这些代码Facade.php<?phpnamespace DesignPatterns\Structural\Facade;class Facade{ /* * @var OsInterface / private $os; /* * @var BiosInterface / private $bios; /* * @param BiosInterface $bios * @param OsInterface $os / public function __construct(BiosInterface $bios, OsInterface $os) { $this->bios = $bios; $this->os = $os; } public function turnOn() { $this->bios->execute(); $this->bios->waitForKeyPress(); $this->bios->launch($this->os); } public function turnOff() { $this->os->halt(); $this->bios->powerDown(); }}OsInterface.php<?phpnamespace DesignPatterns\Structural\Facade;interface OsInterface{ public function halt(); public function getName(): string;}BiosInterface.php<?phpnamespace DesignPatterns\Structural\Facade;interface BiosInterface{ public function execute(); public function waitForKeyPress(); public function launch(OsInterface $os); public function powerDown();}2.8 连贯接口2.8.1 目的用来编写易于阅读的代码,就像自然语言一样(如英语)2.8.2 例子Doctrine2 的 QueryBuilder,就像下面例子中类似PHPUnit 使用连贯接口来创建 mock 对象Yii 框架:CDbCommand 与 CActiveRecord 也使用此模式2.8.3 UML图2.8.4 代码你可以在 GitHub 上找到这些代码Sql.php<?phpnamespace DesignPatterns\Structural\FluentInterface;class Sql{ /* * @var array / private $fields = []; /* * @var array / private $from = []; /* * @var array / private $where = []; public function select(array $fields): Sql { $this->fields = $fields; return $this; } public function from(string $table, string $alias): Sql { $this->from[] = $table.’ AS ‘.$alias; return $this; } public function where(string $condition): Sql { $this->where[] = $condition; return $this; } public function __toString(): string { return sprintf( ‘SELECT %s FROM %s WHERE %s’, join(’, ‘, $this->fields), join(’, ‘, $this->from), join(’ AND ‘, $this->where) ); }}2.9 享元2.9.1 目的为了尽可能减少内存使用,Flyweight与类似的对象共享尽可能多的内存。当使用大量状态相差不大的对象时,就需要它。通常的做法是保持外部数据结构中的状态,并在需要时将其传递给flyweight对象。2.9.2 UML图2.9.3 代码你可以在 GitHub 上找到这些代码FlyweightInterface.php<?phpnamespace DesignPatterns\Structural\Flyweight;interface FlyweightInterface{ public function render(string $extrinsicState): string;}CharacterFlyweight.php<?phpnamespace DesignPatterns\Structural\Flyweight;/* * Implements the flyweight interface and adds storage for intrinsic state, if any. * Instances of concrete flyweights are shared by means of a factory. /class CharacterFlyweight implements FlyweightInterface{ /* * Any state stored by the concrete flyweight must be independent of its context. * For flyweights representing characters, this is usually the corresponding character code. * * @var string / private $name; public function __construct(string $name) { $this->name = $name; } public function render(string $font): string { // Clients supply the context-dependent information that the flyweight needs to draw itself // For flyweights representing characters, extrinsic state usually contains e.g. the font. return sprintf(‘Character %s with font %s’, $this->name, $font); }}FlyweightFactory.php<?phpnamespace DesignPatterns\Structural\Flyweight;/* * A factory manages shared flyweights. Clients should not instantiate them directly, * but let the factory take care of returning existing objects or creating new ones. /class FlyweightFactory implements \Countable{ /* * @var CharacterFlyweight[] / private $pool = []; public function get(string $name): CharacterFlyweight { if (!isset($this->pool[$name])) { $this->pool[$name] = new CharacterFlyweight($name); } return $this->pool[$name]; } public function count(): int { return count($this->pool); }}2.10 代理模式2.10.1 目的为昂贵或者无法复制的资源提供接口。2.10.2 例子Doctrine2 使用代理来实现框架特性(如延迟初始化),同时用户还是使用自己的实体类并且不会使用或者接触到代理2.10.3 UML图2.10.4 代码你可以在 GitHub 上找到这些代码BankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;interface BankAccount{ public function deposit(int $amount); public function getBalance(): int;}HeavyBankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;class HeavyBankAccount implements BankAccount{ /* * @var int[] / private $transactions = []; public function deposit(int $amount) { $this->transactions[] = $amount; } public function getBalance(): int { // this is the heavy part, imagine all the transactions even from // years and decades ago must be fetched from a database or web service // and the balance must be calculated from it return array_sum($this->transactions); }}BankAccountProxy.php<?phpnamespace DesignPatterns\Structural\Proxy;class BankAccountProxy extends HeavyBankAccount implements BankAccount{ /* * @var int / private $balance; public function getBalance(): int { // because calculating balance is so expensive, // the usage of BankAccount::getBalance() is delayed until it really is needed // and will not be calculated again for this instance if ($this->balance === null) { $this->balance = parent::getBalance(); } return $this->balance; }}2.11 注册模式2.11.1 目的要为整个应用程序中经常使用的对象实现中央存储,通常只使用静态方法(或使用单例模式)的抽象类来实现。请记住,这将引入全局状态,这在任何时候都应该避免!而是使用依赖注入来实现它!2.11.2 例子Zend Framework 1: Zend_Registry 持有应用的logger对象,前端控制器等。Yii 框架: CWebApplication 持有所有的应用组件,如 CWebUser, CUrlManager, 等。2.11.3 UML图2.11.4 代码你可以在 GitHub 上找到这些代码Registry.php<?phpnamespace DesignPatterns\Structural\Registry;abstract class Registry{ const LOGGER = ’logger’; /* * this introduces global state in your application which can not be mocked up for testing * and is therefor considered an anti-pattern! Use dependency injection instead! * * @var array / private static $storedValues = []; /* * @var array / private static $allowedKeys = [ self::LOGGER, ]; /* * @param string $key * @param mixed $value * * @return void / public static function set(string $key, $value) { if (!in_array($key, self::$allowedKeys)) { throw new \InvalidArgumentException(‘Invalid key given’); } self::$storedValues[$key] = $value; } /* * @param string $key * * @return mixed */ public static function get(string $key) { if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) { throw new \InvalidArgumentException(‘Invalid key given’); } return self::$storedValues[$key]; }}相关文章:PHP设计模式范例 — DesignPatternsPHP(1)创建型设计模式 ...

January 18, 2019 · 9 min · jiezi