`
javatar
  • 浏览: 1715030 次
  • 性别: Icon_minigender_1
  • 来自: 杭州699号
社区版块
存档分类
最新评论

扩展接口的思考

    博客分类:
  • HTTL
阅读更多
在设计MeteorTL时,
模板指令,表达式操作符的可扩展性,一直没有找到好的方案。
由于扩展是由第三方实现的,所以一般采用SPI(Service Provide Interface)接口的方式。
在留出SPI接口时,引擎需要传递编译期数据及运行时数据给扩展方,
运行时数据肯定是用callback函数的参数列表传递,
但编译期数据是其应持有的状态,所以必需在初始化时给予。
以指令为例:
public interface Directive extends Serializable {

	public void interpret(TemplateContext templateContext) throws DirectiveException;

	public String getName();

	public Expression getExpression();

	public NestDirective getParentDirective();
}

其中,
templateContext 为运行时数据,在callback函数中传过去。
name, expression, parentDirective 为编译期数据,
是在引擎将模板解析成树时,应该注入的。

这里的问题主要在于编译期数据怎么传给扩展者。
下面是我暂时想到的方案。

方案一:
约定构造函数,如:
public class OutDirective implements Directive {

	private String name;

	private Expression expression;

	private NestDirective parentDirective;
	
	public OutDirective(String name, Expression expression, NestDirective parentDirective) {
		this.name = name;
		this.expression = expression;
		this.parentDirective = parentDirective;
	}

	public void interpret(TemplateContext templateContext) throws DirectiveException {
		......
	}

	public String getName() {
		return name;
	}

	public Expression getExpression() {
		return expression;
	}

	public NestDirective getParentDirective() {
		return parentDirective;
	}
}

这种方法契约性太差,实现者可能要在看了文档后才会知道构造函数的约定。

方案二:
指定初始化函数,如:
public interface Directive extends Serializable {

	public void init(String name, Expression expression, NestDirective parentDirective);

	public void interpret(TemplateContext templateContext) throws DirectiveException;

	public String getName();

	public Expression getExpression();

	public NestDirective getParentDirective();
}


public class OutDirective implements Directive {

	private String name;

	private Expression expression;

	private NestDirective parentDirective;
	
	public void init(String name, Expression expression, NestDirective parentDirective) {
		this.name = name;
		this.expression = expression;
		this.parentDirective = parentDirective;
	}

	public void interpret(TemplateContext templateContext) throws DirectiveException {
		......
	}

	public String getName() {
		return name;
	}

	public Expression getExpression() {
		return expression;
	}

	public NestDirective getParentDirective() {
		return parentDirective;
	}
}

这个方案在方案一的基础上改的,使用init函数初始化,当然还必需有无参构造函数。
init函数在接口中定义,是为了保证明确的契约,
但也因为如此,接口暴露了init函数,init可能被非法调用,
因此引擎在初始化指令时,就需要包一层代理:
public class ProxyDirective implements Directive {

	private Directive directive;

	public ProxyDirective(Directive directive) {
		this.directive = directive;
	}
	
	public void init(String name, Expression expression, NestDirective parentDirective) {
		throw new UnsupportedOperationException("此方法为初始化setter,只允许引擎调用!");
	}

	public void interpret(TemplateContext templateContext) throws DirectiveException {
		directive.interpret(templateContext);
	}

	public String getName() {
		return directive.getName();
	}

	public Expression getExpression() {
		return directive.getExpression();
	}

	public NestDirective getParentDirective() {
		return directive.getParentDirective();
	}
}

上面两个方案都有个缺陷是,初始化工作都由引擎完成。
一般会将OutDirective.class的Class类元传给引擎,
当引擎解析到out指令时,就会用Class.newInstance()创建一个实例并初始化它作为指令树的一个节点。

这样第三方在实现OutDirective就会受到诸多限制。
最大的坏处,就是对IoC的破坏,
假如,第三方在实现OutDirective时,需要一些配置,如:默认格式化串等
由于实现者没有指令对象的生命周期管理权,根本没法注入依赖(不管是构造子注入还是setter注入等)。
这就会迫使实现者直接取配置或其它辅助类。
如:
public void interpret(TemplateContext templateContext) throws DirectiveException {
	MyConfig myConfig = MyConfig.getConfig("config.xml");
	ToStringHandler handler = new NotNullToStringHandler();
	......
}


方案三:
使用Handler回调方式
鉴于上面的缺陷,可以将Directive实现为通用类,
通过回调一个Handler接口,
将所有编译期数据和运行时数据都通过callback函数的参数列表传递。
如:
public interface DirectiveHandler {

	public void doInterpret(TemplateContext templateContext, 
		String name, Expression expression, NestDirective parentDirective) 
		throws DirectiveException;

}

public final class Directive implements Serializable {

	private String name;

	private Expression expression;

	private NestDirective parentDirective;

	private DirectiveHandler directiveHandler;
	
	public Directive(String name, Expression expression, NestDirective parentDirective, DirectiveHandler directiveHandler) {
		this.name = name;
		this.expression = expression;
		this.parentDirective = parentDirective;
	}

	public void interpret(TemplateContext templateContext) throws DirectiveException {
		directiveHandler.doInterpret(templateContext, name, expression, parentDirective);
	}

	public String getName() {
		return name;
	}

	public Expression getExpression() {
		return expression;
	}

	public NestDirective getParentDirective() {
		return parentDirective;
	}
}

这样,因为DirectiveHandler不持有状态,
可以只向引擎供应一个实例,而不是类元,对其构造函数也不再有要求。
如:
DirectiveHandler outDirectiveHandler = new OutDirectiveHandler("xxx.xml"); // 构造子注入
outDirectiveHandler.setConfig("xxx.xml"); // setter注入
....
将IoC组装完成后的实例注册到引擎:
Map handlers = new HashMap();
handlers.put("out", outDirectiveHandler);
引擎在解析到out指令时,就会new Directive(name, expression, parentDirective, handlers.get(name));
这样,Directive就变成一个final的内部class,不再是SPI接口。
而换成DirectiveHandler作为SPI接口,其实例的生命周期可自行管理。

暂时只想到这里,
如果有更好的方案请指教,谢谢。
分享到:
评论
12 楼 javatar 2007-07-03  
指令的总体结构是设计成解释器模式的。

指令集被引擎编译成树(合成模式):
Directive和BlockDirective, BlockDirective继承于Directive,
BlockDirective比Directive多一个List<Directive> getInnerDirectives();
Template引用树的根指令,按解释器模式的方式层级调用。

leadyu 写道
你是不是把所有的静态文本都编译成Out指令?执行过程中不断往输出写


是的,静态文本被编译成TextDirective,此指令总是输出其持有的固定文本。TextDirective是包保护级的,不对外公开。
11 楼 leadyu 2007-07-02  
引用
另外,再请教javatar兄一个问题,你引擎在执行一个指令时,比如out,是一次性把整个template的所有out指令都执行完,还是按照template编写的指令顺序一个个执行?如果是后者,好像从接口没看出来,指令怎么识别自己的template的位置?



你是不是把所有的静态文本都编译成Out指令?执行过程中不断往输出写
10 楼 leadyu 2007-07-02  
另外,建议,指令的handle接口没有定义方法,只是一个标志接口,在另外写多几个子接口各自定义方法,毕竟不同类型的指令,可能需要不同的执行接口,不一定是统一的。
9 楼 leadyu 2007-07-02  
呵呵,我只是针对你第二个回复中提出的契约性的矛盾提出一种新的思路而已。我理解你说的问题,一个是指令的逻辑,一个是runtime数据。引擎保存的是Handle实例,handle就代表逻辑,无状态,template代表runtime数据,所以你引擎保存的不是指令的实例。

另外,再请教javatar兄一个问题,你引擎在执行一个指令时,比如out,是一次性把整个template的所有out指令都执行完,还是按照template编写的指令顺序一个个执行?如果是后者,好像从接口没看出来,指令怎么识别自己的template的位置?
8 楼 javatar 2007-07-02  
leadyu 写道
不是很明白为什么要newInstance来实例一个指令?


问题就是你下面一句:

leadyu 写道
t = init.invoke(指令实例,方法参数实例);


这里“指令实例”从哪里来?

而且这个指令实例是每个节点需要新的实例,
如:
@if{xxx}
...@if{yyy}
......
...@end
@end
这里的if指令就有两个实例。

所以指令实例的创建就由引擎负责,
最简单的办法就是Class.newInstance();

用Handler,所有指令可以共享同一Handler(其无状态性,保证单一实例可以在多线程下共享),
所以Handler可以由用户自己创建并传给引擎。
7 楼 leadyu 2007-06-30  
纠正一个笔误,应该init方法要改成protected,否则估计子类通过反射会找不到这个方法,呵呵
6 楼 leadyu 2007-06-29  
指令也应该是一个抽象类,指令肯定会涉及到一些内在行为需要约束,而不是由扩展者随意实现,有了访问私有方法的办法,应该可以解决约束性和封装性,就像你第二个方案提出的矛盾是可以很好解决的,看看jdk的很多模型的实现基本都采用这种方式。
5 楼 leadyu 2007-06-29  
就是说,引擎能够提供什么数据给指令都是由引擎调用指令的私有init方法传递给指令的实现类,实现类自己有什么其他属性需要构造由它自己来定,不是很明白为什么要newInstance来实例一个指令?
4 楼 leadyu 2007-06-29  
>引擎只能通过Class.newInstance()方式创建这些实例

可能我没表达清楚我的意思,我的意思是引擎保存指令的实例,实例怎么构造很随便,由实现者构思,那么在指令被执行前,由引擎调用init方法,引擎怎么调用私有方法,很简单,如下面例子:


Class ll = Class.forName("*.*.Directive");  
Method init= ll.getDeclaredMethod("init",参数Class);

//设置安全访问权限级别,允许访问私有方法
init.setAccessible(true);
		
t = init.invoke(指令实例,方法参数实例);
3 楼 javatar 2007-06-29  
谢谢你的提议。
init是可以通过关闭类的访问级隐藏(如PrivateAccesser工具类的做法),但Directive做为抽象类,还有一点就是会限制他的子类必需是无参构造函数的。因为指令树的每一个节点都需要一个新的实例。引擎只能通过Class.newInstance()方式创建这些实例。如果用Handler就可以避免。
2 楼 leadyu 2007-06-27  
而且,这样做,有个好处,可以把对于指令的一些想约束的逻辑封装,只暴露那些可以给第三方扩展的方法,由这些方法行为定义新的指令,保证引擎的健壮和安全。
1 楼 leadyu 2007-06-27  
对于指令的包装,我个人觉得,可以不采用接口,把Directive变成抽象类,所有指令都继承它,ini方法变成私有,这个方法,只由引擎通过反射去调用它,这样就可以保证指令初始的封装性了。

相关推荐

    ### 2024年第一季度青岛房地产市场季度简报总结、市场综述

    2024年第一季度,青岛房地产市场经历了显著变化,总体呈现供需双降的趋势。一季度全市商品房新增10,721套,面积约152.04万平方米,同比下降29%;销量为14,936套,面积约200.85万平方米,同比下降38%,成交均价为14,204元/平方米,同比下降2%。土地市场方面,供应总量为39万平方米,同比减少7%,但成交面积为27万平方米,同比增长31%,楼面地价为6,625元/平方米,同比增长253%,土地出让金为17.61亿元,同比增长354%。二手房市场新增挂牌2.9万套,成交13,405套,132.21万平方米,累计挂牌51.70万套,挂牌均价17,800元/平方米。此外,青岛市出台多项政策支持房地产市场平稳健康发展,包括降低房贷利率、优化开发用地土地规划政策、支持房企融资等。这些政策旨在促进市场供需平衡,防止市场大起大落。

    Linux常用命令大全.markdown

    linux常用命令大全

    MATLAB代码,用于模拟具有无限半空间体积导体的电机单元电势(MUP),星号.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    空调销售网站策划案例.doc

    空调销售网站策划案例.doc

    全球6G技术大会2024年以用户为中心的6G接入网技术研究白皮书31页.pdf

    全球6G技术大会2024年以用户为中心的6G接入网技术研究白皮书31页.pdf

    简约专业风格毕业答辩模板47个

    简约专业风格毕业答辩模板是一系列专为追求简洁与高效表达的大学生设计的答辩文档模板,共47个。这些模板融合了经典的设计元素与现代审美,强调信息的清晰传递与视觉的整洁,旨在帮助学生在答辩中以最专业的面貌展示自己的研究成果。 每个模板都具备结构合理的布局,适用于各个学科和研究领域,从人文社科到自然科学,均能满足不同需求。简约风格的设计使得学生能够专注于内容本身,避免冗余信息的干扰,提升答辩的专业性和可信度。此外,模板中合理运用的色彩、字体和图表设计,不仅增强了视觉吸引力,也使信息更易于理解。 通过使用这些简约专业风格的毕业答辩模板,毕业生能够自信地呈现自己的学术成果,提升答辩的整体效果,为成功的学术交流打下坚实基础。这些模板是展示个人研究与风格的理想选择。

    【数据集和模型】ChatGPT文本二分类

    由 Epsilon Luoo 在 HC3-Chinese 的基础上进行了一些细微的修改和清洗

    数字人动作捕捉:MATLAB-Kinect骨骼数据实时插值算法.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 你是否渴望高效解决复杂的数学计算、数据分析难题?MATLAB 就是你的得力助手!作为一款强大的技术计算软件,MATLAB 集数值分析、矩阵运算、信号处理等多功能于一身,广泛应用于工程、科学研究等众多领域。 其简洁直观的编程环境,让代码编写如同行云流水。丰富的函数库和工具箱,为你节省大量时间和精力。无论是新手入门,还是资深专家,都能借助 MATLAB 挖掘数据背后的价值,创新科技成果。别再犹豫,拥抱 MATLAB,开启你的科技探索之旅!

    HI3519DV500 配置无线网依赖库以及编译脚本

    HI3519DV500 配置无线网依赖库以及编译脚本

    定制小米8-lineage22.1安卓15-fast功能项目线刷双版root 解锁bl后fast线刷

    资源说明; 1-----刷写前提是手机必须解锁bl先。而且会在fast模式刷写固件 2-----刷写方法与官方刷写步骤一样 3-----此固件为定制初始固件。可以在fast模式刷写 4-----属于适配固件。也许有个别bug。不接受请勿下载 5-----需要一定的刷机常识与动手能力的友友刷写。 6-----资源有可复制性。下载后不支持退。请知悉 7-----定制其他需求可以在csdn私信博主 博文参阅:https://csdn9.blog.csdn.net/article/details/143058308

    【机械臂路径规划】基于matlab快速探索随机树RRT和概率路网PRM串联机械臂路径规划【含Matlab源码 13167期】.zip

    Matlab领域上传的视频是由对应的完整代码运行得来的,完整代码皆可运行,亲测可用,适合小白; 1、从视频里可见完整代码的内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    世邦魏理仕:2021年西安房地产市场回顾与2022年展望.pdf

    世邦魏理仕:2021年西安房地产市场回顾与2022年展望

    Android Studio 2022.1.1和java编程语言yinyuebofangqi.zip

    Android Studio 2022.1.1和java编程语言yinyuebofangqi

    C知道对话分享图片下载

    C知道对话分享图片

    png-jpg-gif-webp-tiff等图片压缩工具基于nodejs的实现

    png-jpg-gif-webp-tiff等图片压缩工具基于nodejs的实现,绿色本地免安装,解压后运行exe文件,将图片文件或者包含图片的文件夹拖拽到软件界面即可压缩

    派对屋A1效果器电脑调音软件

    我们要了解什么是DSP(Digital Signal Processing)。DSP即数字信号处理,是一种利用数字计算方法对信号进行分析、变换和操作的技术。在汽车音响领域,DSP被广泛应用于改善音质,通过调整频率响应、延时、相位和增益等参数,使声音更加均衡、立体。 惠威是一款数字信号处理器,适用于那些希望升级原车音响系统但预算有限的用户。它通常拥有多个输入和输出接口,可以连接到汽车的音频源和扬声器,通过软件进行调音,使得声音能够适应不同的驾驶环境和听音偏好。 ,集成了先进的噪声抑制技术和强大的功率放大器,旨在为发烧友级别的车载音响系统提供卓越的性能。用户可以通过软件对整个系统的每一个细节进行优化,包括主动分频、时间校正等,以达到Hi-Fi级别的音乐享受。

    通信工程分包合同.docx

    通信工程分包合同.docx

    demo1(1).py

    demo1(1).py

    金融量化交易:MATLAB_构建多因子选股模型的完整开发指南.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 你是否渴望高效解决复杂的数学计算、数据分析难题?MATLAB 就是你的得力助手!作为一款强大的技术计算软件,MATLAB 集数值分析、矩阵运算、信号处理等多功能于一身,广泛应用于工程、科学研究等众多领域。 其简洁直观的编程环境,让代码编写如同行云流水。丰富的函数库和工具箱,为你节省大量时间和精力。无论是新手入门,还是资深专家,都能借助 MATLAB 挖掘数据背后的价值,创新科技成果。别再犹豫,拥抱 MATLAB,开启你的科技探索之旅!

    深度学习注意力机制:MATLAB_实现多模态医学影像融合诊断.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 你是否渴望高效解决复杂的数学计算、数据分析难题?MATLAB 就是你的得力助手!作为一款强大的技术计算软件,MATLAB 集数值分析、矩阵运算、信号处理等多功能于一身,广泛应用于工程、科学研究等众多领域。 其简洁直观的编程环境,让代码编写如同行云流水。丰富的函数库和工具箱,为你节省大量时间和精力。无论是新手入门,还是资深专家,都能借助 MATLAB 挖掘数据背后的价值,创新科技成果。别再犹豫,拥抱 MATLAB,开启你的科技探索之旅!

Global site tag (gtag.js) - Google Analytics