ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

[DeepSeek Harness插件内核-09]服务的多种定义的形式

[DeepSeek Harness插件内核-09]服务的多种定义的形式 DeepSeek Harness插件内核-02:服务的注册与消费中我们简单体验了一下如何在Cordis中定义、注册和使用服务在DeepSeek Harness插件内核-08:利用RefectService以反射方式扩展Context中介绍的ReflectService的核心作用还是集中在服务的注册上。为插件提供复用功能的服务在Corids中具有重要的意思而且服务本身也可以作为插件形式被注册本篇文章将对服务展开全面介绍。1. 使用常规类型虽然Cordis为服务定义了名为Service的基类但是却没有要求注册的服务强制继承该基类原则任何一个对象都可以注册为服务。为了更加深刻地理解服务在正是介绍Service这个基类之前我们选在将服务定义成一个普通的类型。比如我们定义了如这个普通类型Logger作为用来记录日志的基础服务类似于现有的LoggerService。为了让它具有自动跟踪当前Context的能力我们必须在symbols.tracker属性上设置一个Tracker对象并利用property设置绑定当前Context的属性名。如果不需要携带在构造函数中提供的原始Context我们最好将noShadow属性设置为true。由于构造函数将指定的Context绑定到了私有属性ctx中所以用来记录日志的write方法就能使用这个属性得到当前Context的信息这里我利用它得到当前插件的名称并将其输出到日志中。import{Context,symbols,Tracker}fromdeepseek-ai/cordisdeclaremoduledeepseek-ai/cordis{interfaceContext{log:Logger}}classLogger{[symbols.tracker]:Tracker{property:ctx,noShadow:true}write(message:string,severity:trace|debug|info|warn|error){console.log([${severity}][${this.ctx.fiber.runtime?.name}]${message});}constructor(privatectx:Context){}}constctxnewContext();ctx.provide(log,newLogger(ctx));ctx.plugin({name:Plugin A,apply:ctxctx.log.write(Plugin A starts,info)});ctx.plugin(ctxctx.log.write(Plugin B starts,info));process.stdin.resume();输出[info][Plugin A]Plugin A starts[info][]Plugin B starts从输出可以看出由于插件具有三种定义形式对于直接以函数注册的插件其实是没有名称的所以针对第二个插件的日志中将会显式该部分的内容。2. 将配置分离出来将服务的执行逻辑和配置分离有配置动态控制服务的执行行为在很多情况下都是必要同时也是推荐的做法。通过DeepSeek Harness插件内核-06:Context全面解析的介绍我们知道服务的配置被专门存储在Conext的symbols.intercepts属性中我们只需要调用Context的intercept方法通过指定的服务名称和配置对象创建一个子Context就行。所以我们选择将上面定义的Logger服务添加一个结构为{source:string}的配置并使用插件名称作为兜底选项。由于第二个插件是在我们创建的子Context上注册的创建它针对intercept方法的调用中为log服务指定了正确的配置。import{Context,symbols,Tracker}fromdeepseek-ai/cordisdeclaremoduledeepseek-ai/cordis{interfaceContext{log:Logger}}classLogger{[symbols.tracker]:Tracker{property:ctx,noShadow:true}write(message:string,severity:trace|debug|info|warn|error){constconfig(this.ctx[symbols.intercept]asany|undefined)?.log;constsource(configas{source:string})?.source??this.ctx.fiber.runtime?.name;console.log([${severity}][${source}]${message});}constructor(privatectx:Context){}}constctxnewContext();ctx.provide(log,newLogger(ctx));ctx.plugin({name:Plugin A,apply:ctxctx.log.write(Plugin A starts,info)});constsubContextctx.intercept(log,{source:Plugin B});subContext.plugin(ctxctx.log.write(Plugin B starts,info));输出[info][Plugin A]Plugin A starts[info][Plugin B]Plugin B starts3. 继承基类Service在大部分情况下我们推荐将服务定义为继承自Service基类的形式因为这样确实可以减少很多重复的代码。Service基类在构造函数中将Context绑定到了ctx属性上并且在symbols.tracker属性上设置了一个Tracker对象property属性为ctxnoShadow属性为true所以我们可以直接使用ctx属性来访问当前Context。上面定义的Logger服务可以直接继承Service基类并在构造函数中调用super(ctx, log)来完成服务的注册。import{Context,symbols,Service}fromdeepseek-ai/cordisdeclaremoduledeepseek-ai/cordis{interfaceContext{log:Logger}}classLoggerextendsService{write(message:string,severity:trace|debug|info|warn|error){constconfig(this.ctx[symbols.intercept]asany|undefined)?.log;constsource(configas{source:string})?.source??this.ctx.fiber.runtime?.name;console.log([${severity}][${source}]${message});}constructor(ctx:Context){super(ctx,log);}}constctxnewContext();newLogger(ctx);ctx.plugin({name:Plugin A,apply:ctxctx.log.write(Plugin A starts,info)});constsubContextctx.intercept(log,{source:Plugin B});subContext.plugin(ctxctx.log.write(Plugin B starts,info));输出[info][Plugin A]Plugin A starts[info][Plugin B]Plugin B starts4. 让服务实例变成可同时调用的函数如果我们为服务类型定义一个通过symbols.invoke属性标记的方法我们就可以将服务实例当作一个函数来调用这样就可以让服务的使用更加简洁。我们在Logger服务中定义了一个symbols.invoke属性标记的write方法这样就可以直接通过log服务实例来调用write方法。import{Context,symbols,Service}fromdeepseek-ai/cordisdeclaremoduledeepseek-ai/cordis{interfaceContext{log:Logger((message:string,severity:trace|debug|info|warn|error)void)}}classLoggerextendsService{write(message:string,severity:trace|debug|info|warn|error){constconfig(this.ctx[symbols.intercept]asany|undefined)?.log;constsource(configas{source:string})?.source??this.ctx.fiber.runtime?.name;console.log([${severity}][${source}]${message});}constructor(ctx:Context){super(ctx,log);}[symbols.invoke](message:string,severity:trace|debug|info|warn|error){this.write(message,severity);}}constctxnewContext();newLogger(ctx);ctx.plugin({name:Plugin A,apply:ctxctx.log(Plugin A starts,info)});constsubContextctx.intercept(log,{source:Plugin B});subContext.plugin(ctxctx.log(Plugin B starts,info));输出[info][Plugin A]Plugin A starts[info][Plugin B]Plugin B starts5. 基类Service的定义我们最后来看看作为基类的Service的定义。这是一个泛型类类型参数T代表服务的配置类型。Service基类在构造函数中将Context绑定到了ctx属性上并且在symbols.tracker属性上设置了一个Tracker对象property属性为ctxnoShadow属性为true所以我们可以直接使用ctx属性来访问当前Context。由于构造函数内部会自动调用Context的provide方法将服务注册到当前Context中所以我们不需要在服务的构造函数中显式调用provide方法。我们可以调用构造函数的第二个参数来指定服务的名称如果不指定则会使用provide静态属性作为服务的名称。exportabstractclassServiceoutTnever{declare[symbols.config]:Tpublicname!:stringconstructor(protectedctx:Context,name:string){name??this.constructor[provide]asstringletselfthisconsttracker:Tracker{associate:name,property:ctx,}if(self[symbols.invoke]){selfcreateCallable(name,joinPrototype(Object.getPrototypeOf(this),Function.prototype),tracker)}self.ctxctx self.namenamedefineProperty(self,symbols.tracker,tracker)self.ctx.reflect.provide(name,self,this[symbols.check])returnself}}Service将symbols.invoke属性标记的方法作为服务实例的默认调用方法实现在针对createCallable函数的调用上。下面的代码提供了针对createCallable函数的实现可以看出整个实现很简单它将服务实例包装成一个可调用的函数并在调用时创建一个跟踪当前Context的代理对象。exportfunctioncreateCallable(name:string,proto:{},tracker:Tracker){constselffunction(...args:any[]){constproxycreateTraceable(self[ctx],self,tracker)returnapplyTraceable(proxy,self,this,args)}defineProperty(self,name,name)returnObject.setPrototypeOf(self,proto)}functionapplyTraceable(proxy:any,value:any,thisArg:any,args:any[]){if(!value[symbols.invoke])returnReflect.apply(value,thisArg,args)returnvalue[symbols.invoke].apply(proxy,args)}Service还定义了如下这些利用预定义Symbol命名的受保护方法供继承它的类型使用。exportabstractclassServiceoutTnever{protected[symbols.filter](ctx:Context){returnctx[symbols.isolate][this.name]this.ctx[symbols.isolate][this.name]}protected[symbols.extend](props?:any){letself:anyif(this[Service.invoke]){selfcreateCallable(this.name,this,this[symbols.tracker])}else{selfObject.create(this)}returnObject.assign(self,props)}[symbols.resolveConfig](base?:T,head?:T):T{letinterceptthis.ctx[Context.intercept]constconfigs:any[][]while(this.nameinintercept){if(Object.hasOwn(intercept,this.name)){configs.unshift(intercept[this.name])}interceptObject.getPrototypeOf(intercept)}if(base)configs.unshift(base)if(head)configs.push(head)if(this[Config]?.merge){returnthis[Config].merge(...configs)}else{returnObject.assign({},...configs)}}}具体的方法说明如下[symbols.filter]用于判断当前Context是否与服务实例的原始Context相同如果相同则返回true否则返回false。这个方法主要用于在服务实例被继承时判断当前Context是否与服务实例的原始Context相同从而决定是否需要创建一个新的服务实例[symbols.extend]用于创建一个新的服务实例并将当前服务实例的属性复制到新的服务实例上。这个方法主要用于在服务实例被继承时创建一个新的服务实例从而实现服务的复用[symbols.resolveConfig]用于解析服务的配置返回一个合并后的配置对象。这个方法主要用于在服务实例被继承时解析服务的配置从而实现服务的复用。除此以外Service还定义了一个通过Symbol.hasInstance属性标记的静态方法用于判断一个对象是否是指定服务类型的实例。从代码可以看出该方法通过判断对象的原型链上是否存在指定服务类型的构造函数来确定对象是否是指定服务类型的实例。exportabstractclassServiceoutTnever{static[Symbol.hasInstance](instance:any){if(!instance)returnfalseletconstructorinstance.constructorwhile(constructor){constructorconstructor.prototype?.constructorif(constructorthis)returntrueconstructorObject.getPrototypeOf(constructor)}returnfalse}}
返回列表