`
javatar
  • 浏览: 1705272 次
  • 性别: 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方法变成私有,这个方法,只由引擎通过反射去调用它,这样就可以保证指令初始的封装性了。

相关推荐

    软件接口的哲学思考与开发实例

    通过学习“软件接口的哲学思考”,我们可以提升对接口设计理念的理解,从而更好地构建可维护、可扩展的系统。在“0-软件接口的哲学思考.ppt”这个文件中,可能会详细讲解这些概念,并提供实际案例来加深理解。接口...

    计算机接口技术 思考题答案

    常见的I/O接口有串行接口(如RS-232)、并行接口(如IEEE 1284)、USB(通用串行总线)、PCI(外围组件互联)和PCIe(PCI扩展)等。这些接口都有其特定的数据传输速率和适用场景。 3. DMA(直接存储器访问):DMA是...

    类的继承和接口的扩展优秀文档.ppt

    **类的扩展和接口的扩展总结:** 类的继承和接口的扩展是Java中实现代码复用和设计灵活性的关键。继承允许我们创建基于现有类的子类,以增加或修改特性,而接口则提供了定义行为规范而不涉及实现的方式。合理使用这...

    java抽象类与接口的深入思考

    ### Java抽象类与接口的深入思考 在Java编程语言中,抽象类(abstract class)与接口(interface)是实现多态性以及代码复用的重要工具。本文将通过具体实例来探讨这两种概念的区别及其应用场景,帮助读者更好地...

    DSP接口电路设计与编程

    介绍了以数字信号处理器(DSP)为核心的实时数字信号处理的系统设计,详细论述了DSP与多种外围接口电路的设计方法,包括各种存储器、模数和数模转换电路、异步串行接口、地址/数据复用总线、扩展I/O、CPCI总线,以及...

    面向接口编程理解demo

    在实践中,开发者应时刻思考如何通过接口来定义和组织功能,从而提高代码质量。在提供的"test2"文件中,可能包含了演示面向接口编程的示例代码,通过学习和分析这些代码,可以进一步加深对这一概念的理解。

    微机原理 静态存储器扩展实验 实验报告

    总结:本实验不仅让学习者掌握了静态存储器扩展的基本技能,还强化了他们对微机原理及接口技术的理解,为后续深入学习和实践奠定了坚实基础。通过反复练习和理论分析,可以进一步提升解决实际问题的能力。

    从8086到Pentium Ⅲ微型计算机及接口技术1

    5.5 存储器的扩展 5.6 微机内存层次结构 5.7 微机系统中的其他存储部件 5.8 微型计算机系统的内存管理 思考与习题 第6章 输入输出基础 6.1 概述 6.2 输入输出控制方式 6.3 I/O接口的基本结构及特点 6.4 I/O接口的...

    接口实验报告 接口与通信实验

    - 8255是一种通用并行接口芯片,通常用于扩展微处理器的I/O功能。实验中,学生将学习如何配置8255的工作模式,以及如何通过它进行数据的输入和输出操作。 3. **数字式时钟(电子钟)实验** - 电子钟是基于特定...

    8155键盘显示接口设计

    电路设计包含了并行接口扩展和串行接口的运用。串行接口扩展字形口是一种节省单片机端口开销的常用的方法。 四、键盘接口电路设计 键盘接口电路设计包含了8155和74LS164的使用。键盘行列端口连接到8155的PC3-PC0,...

    微型计算机原理与接口技术(第三版)电子书及答案周荷琴

    第1章 微型计算机概述 1 1.1 微型计算机的发展与应用 1 1.1.1 微处理器的产生和发展 1 1.1.2 微型计算机的分类 4 1.1.3 微型计算机的特点与性能指标 4 1.1.4 微型计算机的应用 6 ...思考与练习题 324

    从8086到Pentium Ⅲ微型计算机及接口技术5

    5.5 存储器的扩展 5.6 微机内存层次结构 5.7 微机系统中的其他存储部件 5.8 微型计算机系统的内存管理 思考与习题 第6章 输入输出基础 6.1 概述 6.2 输入输出控制方式 6.3 I/O接口的基本结构及特点 6.4 I/O接口的...

    loadrunner接口性能脚本

    在IT行业中,性能测试是确保系统稳定性和可扩展性的重要环节。LoadRunner是一款业界广泛使用的性能测试工具,尤其适用于接口性能测试。本文将详细探讨"loadrunner接口性能脚本"的相关知识点,帮助性能测试人员更好地...

    ZVT是对fooltrader重新思考后编写的量化项目其包含可扩展的数据recorderapi因子计算选股回测交易以及统一可视化

    ZVT是对fooltrader重新思考后编写的量化项目,其包含可扩展的数据recorder,api,因子计算,选股,回测,交易,以及统一的可视化,定位为中低频 多级别 多因子 多标的 全市场分析和交易框架。ZVT是基于对fooltrader...

    从8086到Pentium Ⅲ微型计算机及接口技术4

    5.5 存储器的扩展 5.6 微机内存层次结构 5.7 微机系统中的其他存储部件 5.8 微型计算机系统的内存管理 思考与习题 第6章 输入输出基础 6.1 概述 6.2 输入输出控制方式 6.3 I/O接口的基本结构及特点 6.4 I/O接口的...

    单片机原理及接口技术课后习题第9章 答案.pdf

    第九章复习思考题4中设计了使用 74LS273 和 74LS244 为 8051 单片机扩展 8 路输入与 8 路输出接口的接口电路,并编制了检验控制程式。 第九章复习思考题5中使用 74LS377 作为 8 位触发器,功能表见表 9-17,采用它...

    从8086到Pentium Ⅲ微型计算机及接口技术 2

    5.5 存储器的扩展 5.6 微机内存层次结构 5.7 微机系统中的其他存储部件 5.8 微型计算机系统的内存管理 思考与习题 第6章 输入输出基础 6.1 概述 6.2 输入输出控制方式 6.3 I/O接口的基本结构及特点 6.4 I/O接口的...

    微机原理与接口技术

    《微机原理与接口技术》是一门深入探讨计算机硬件与软件交互的专业课程,主要研究微型计算机的基本结构、工作原理以及如何通过接口进行扩展和通信。李继灿教授编写的教材在该领域广受欢迎,因其清晰的讲解和丰富的...

    使用Python扩展PSSE,python常用扩展库

    在描述中提到的“一个初步的仿真案例供读者学习思考”,意味着这个文档可能包含了一个基础的示例,用于指导初学者如何利用Python来操作PSSE进行电力系统仿真。这样的案例通常会涵盖从安装必要的Python库,如`psspy`...

Global site tag (gtag.js) - Google Analytics