总体来说,用户界面与业务模型的互动牵扯很多
配置信息(比如,当使用一个选择的时候,可以用 Combo,也可以用 List,完全处于美观或者方便的需要,具体使用哪一个是用户的决定,而这个决定即属于上面所说的配置信息,再比如说,使用一个文本输入的时候,可以用单行输入,也可以多行输入,还可以按照密码输入,也可以出于各方面因素的考虑,也是配置信息)。另外还需要很多
搭建/配置行为,包括初始化控件,配置控件,将控件与业务模型进行连接,配置数据校验以及类型转换等等行为。除了一些特殊行为,这些行为应该多数由工具包提供。
工具包应该实现一个灵活的 FormFieldFactory。FormFieldFactory 根据一个 context 来生成 Field,对 Field 进行各种配置,并且将 Field 和 Property 进行合理的连接,以便实现 validate 和 type conversion等各种操作。FormFieldFactory 实际上做得很少,也没有任何灵活性。灵活性全部在 context 中。具体而言,context 包含了上述
配置信息和
搭建/配置行为。配置信息方面,context包含了用来生成 Field 的各个相关元素(也就是下面要说到的默认信息)以及用户的指示等等。搭建/配置方面,context包含了具体实现的 command。通常是一个 command chain,由用户提供或者指定。但是,工具包应该提供一个或者多个通用的 command,供用户在大多数情况下使用。
主要的考虑有以下几点:
工具包提供的默认行为应该可以在大多数情况下满足需要。
仅仅特殊的指示和特殊的操作需要用户提供配置信息和操作的实现。
以上配置信息和搭建/配置行为应该互相分开。
配置信息 / 搭建配置行为各自在一个适合的 / 集中的地方进行配置,以便管理。
配置信息以及搭建/配置行为应该灵活,容易扩展。
关于配置信息的公开界面的考虑:
配置信息是要可以被各个搭建/配置行为的具体实现查询到的。各个搭建/配置行为的具体实现会问这样的问题:
我正在处理这个context(包含了正在为了哪一个form,哪一个Item,哪一个propertyId,来生成Field),希望获得这样的指示(比如,“Field 应该用哪个具体实现类?”)。因此,界面可以是:
Context {
Object getConfigurationInfo(Object configurationType)
// 以下仅仅是为了免得客户进行类型转换
String getStringConfigurationInfo(Object configurationType)
}
这样,Context 会有很多具体代码,但是省掉了各个搭建/配置行为具体实现去写重复的代码来或者这些关于Context的一些进一步信息。
关于配置信息的考虑
配置信息分为默认信息和特定指示信息。如果一个搭建/配置行为同时参考特定指示信息和默认信息,应该让特定指示信息优先于默认信息。
默认信息是本来就存在的,随着业务模型或者UI等于生俱来的(比如业务模型的类的名称,属性的名称等),或者虽然是用户配置的信息,但不是专门为生成用户界面而准备的信息(比如hibernate 的元数据)。默认信息不需要配置或者至少不需要专门配置,因此可以很分散,总之让搭建/配置程序可以得到即可。默认信息应该可以允许搭建/配置程序在多数情况下生成可以使用的,不需要进一步修改的控件。
特定指示信息与默认信息相反,是专门为了搭建/配置用户界面而准备的,应该集中设置和管理,这样比较方便查找和修改。特定指示信息应该仅仅在特殊情况下用来进行非常规配置(比如虽然业务模型是一个String类型的属性,但是用选择来进行输入),或者对默认配置的进一步修饰(比如默认用选择控件,但指示信息进一步指示使用哪一样选择控件)。
指示信息应该集中,从而方便管理。指示信息还应该尽量通用,即不是仅仅为了某搭建/配置程序而准备(虽然可能经常如此),而是为了描述用户所希望的结果。指示信息还应该便于各个搭建/配置程序查询。
关于默认信息的具体考虑
FormFieldFactory 可以得到的各种信息都可以成为默认信息的来源,它们包括:
Item - 在与 Hibernate 进行结合的时候应该是 BeanItem,那么 getBean() 可以得到 bean,进一步可以得到其 class, Hibernate 的 metadata,以及一些标记。这些 class, metadata 以及标记应该都可以作为智能生成 Field 的一些判断。但是,bean 属于model,不应该过分地将表现层的东西(比如文本框的大小什么的)放到这里。如果某些属性是与 model 相关而不因表现层改变的,比如密码不论用什么表现层都应该按照密码输入对待,那么在这里进行标注似乎是合理的。
id - BeanItem 使用属性的名字作为 id,可以作为判断条件,可以结合 bean 的类进行判断。
Form - 似乎没有什么特别可以作为判断条件的。但不妨作为参数传递给处理器。
默认信息需要用代码来获得,具体实现可以先在 Context 上面来实现,直接把获得具体信息的代码写在 Context 上面。当然,属于对这些信息进一步分析的代码就应该由具体的搭建/配置实现的代码来完成了。
关于特定指示信息的具体考虑
特定指示信息应该放在 context 里面,从而可以由所有的搭建/配置的具体实现单元参考。作为一种具体实现,指示信息可以按照这样的双层方式组织:第一层是以作为信息类型的Object为索引的Map,第二层按照以下几种方式查询/设置(按照以下次序,最特殊的在前,最通用的在后,以解决冲突问题):
关于某 bean class / 某 property
关于某 property (这两者都在 Context 里面有,因此完全可以 Context 自行得到)
仅仅在用到的时候实现:符合某种 condition - 使用一个 condition framework (这个可以由用户在指定)
关于搭建/配置行为的实现的考虑
为了使得这些行为尽可能得可以重用,这些行为应该按照上面的配置信息(默认信息以及特定指示信息)进行动作。
行为可以考虑用 chain of responsibility 模式实现。工具包提供很多行为的实现(command),还提供一个或者几个一般用途的 chain 共用户选择使用。用户也可以在具体应用中提供特殊 chain,两者进行组合来实现。
关于具体的要完成的功能以及如何完成的考虑
要完成的功能 |
如何完成 |
生成 Form 中的 Field |
默认行为和 Property 的类型有很大关系,比如
enum 应该用选择控件,
boolean 用checkbox,
String 用文本输入框
Date 用日期输入框
数值类型用文本输入框
特殊行为根据用户指示,比如 enum 的选择控件使用 ComboBox 还是 ListSelect。具体的做法可以是在特定指示信息中指定具体的类来操作。
|
在 Field 之间和 pojo entity 之间进行连接以及数据类型转换 |
需要考虑 Field 在 commit() 的时候将何种类型的值传送给 datasource.setValue()。按照 AbstractField.commit()源代码,应该是getValue()。按照Field接口的文档,getValue()的类型应该是 getType()。因此,可以考虑以下实现:
如果类型相符 - 就是说 field.getType() == datasource.getType(),可以直接连接,不需要进行类型转换。
如果类型不相符,则应该连接一个类型转化器,连接在两个 Property 之间。
converter.setValue() 接受 field.getType()类型参数,然后进行转换,再调用 datasource.setValue()。反之,converter.getValue() 从 datasource.getValue() 取得值,然后进行转换,再返回给调用者。 converter 应该实现 property 接口。
默认实现可以维护一组转化器,自动在 Field 和 data source 之间搭建类型转换器。
|
对 Field 进行装饰 - 比如定义文本栏的尺寸大小,secret属性,等等 |
默认行为可以不进行任何修饰或者进行默认修饰。其他的可以在特定指示中查找,如果有特定指示,则进行修饰。
|
设置 Field 的 Caption |
默认的实现可以将 bean name + property id 当作键值,然后查找一个设定的 resource boundle。
|
对各个 Field 的 validate
|
默认行为,可以查看是否 hibernate validate 或者其他 validation framework 的 validate 适用,如果适用,则生成一个对应的 validator 适配器,进行 validate。
|
对 form 进行的 validate
|
默认行为:如果 form.getItemDataSource()是一个BeanItem,getBean返回一个 hibernate 管理的 bean,可以自动生成一个适配器 validator 给 form。
|
权限控制
|
根据用户的 id, roles, 以及 bean/property id,自动查找该用户对应的权限。
|
|
|
|
|
|
|
|
|