论坛首页 Java企业应用论坛

JSF的加减法与Seam(二)之对java的改进

浏览 17212 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-11-06  
3.1.3 接口即类

在软件开发的时候,往往很难决定一项功能究竟是做成实实在在的类,还是做成接口以防以后的扩展或者替换。做成类得话能省去很多麻烦,但是会造成依赖它的类对该类的signature(类名,方法名等等)有绑死的依赖。如果被依赖类被非常多的类依赖得话,以后万一想作改动就会造成大面积的牵动。而如果对每个子功能都作成接口得话,则会大大增加系统的复杂度。

在以往应用的做法中,一般是只把预计以后可能变动的功能提出来被接口(或抽象类)封装,而对可能不会发生变动的功能就使用直接类的方式。这种方式是在过去的编程模式下比较好的一种折衷,但是它也有弊端。人的预计能力是有限的,很常见预计变动的功能最后没有变动,而预计不大可能发生变动的功能结果最后却发现需要替换。

在全局域管理下,有一种新的更方便的解决方法。对于代码:
@Name("config")
public class Config {
    @In Parser parser;
    private List<Module> modules;
    public void loadParser(){
        modules = parser.parser()
    }
    public String get(String moduleName, String configName) {
        //通过指定配置模块的名称和配置项的名称来查找某个配置的设定
    }
}

@Name("parser")
public class Parser {
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


这是一个简单的例子,Config类是整个应用的配置类,其它类可以通过它获取某项配置的内容。配置文件可能比较大,所以被分割成若干模块(Module),通过指定模块名和在模块内的配置名来查找某项配置的内容。Config类依赖于Parser类,通过Parser来把一个配置文件加载并分割成若干模块。

回到我们刚才的问题,这里我们把被依赖类Parser做成了一个类而非接口,那么假如以后我们想实现其它种类的Parser该怎么办呢?以往应用中我们不得不更改所有依赖类里面和被依赖类的衔接代码,但是在Seam里,这样的代码不需要,我们只需要改动被依赖类就可以了。
public interface Parser {
    List<Module> parser();
}

@Name("obsoleteParser")
public class PropertyParser implements Parser{
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}

@Name("parser")
public class XMLParser implements Parser{
    private static final String CONFIG_FILE = "org/javaeye/resources/config.xml";
    List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


我们看到,依赖类里面一行代码都没有被改动,我们就完成了所有替换。在实际应用中如果依赖类有非常多,那么这样的节省是巨大的。
我们也看到,在对被依赖类Parser进行变动的时候,我们把原来的类改成了接口,并把原来的实现放在了接口Parser的子类中。我们把原来的对property文件进行解析的实现命名为"obsoleteParser"表示这种实现已经作废,而把新的实现XMLParser命名为原来的名字parser。因为所有的依赖类都是根据"parser"这个名字来衔接的,对Parser类的改动不会影响依赖类的代码。

这里我们又看到了一个有趣的现象,在全局域管理的模式下,所有的类都变得像接口了。我们不用再考虑是否把某个功能抽象出来做成接口,直接用类实现就可以了。反正在以后把这些类重写成接口也是一件容易的事。

当然,接口的淡化并非是舍弃接口不用,这样搁置接口的能力,恰恰是接口本身的封装能力所决定了的。而且也并非所有的接口都可以这样使用。接口有两种,一种它的不同实现的替换是发生在编译时(compile time)的,就像上面的这个例子,是可以使用这样的方法的;一种它的不同实现的替换是发生在运行时(run time)的,则不可以使用这样的方法。

比如,如果一个应用有个User接口,它需要同时支持NormalUser和Administrator,这样的应用就不可能只写类不写接口。不到运行的时候,应用是无法知道究竟需要的是NormalUser 还是Administrator的。但是这样的应用是很好做决定是否使用接口的,通常这来自于功能需求而不是扩展与变动的需要。
0 请登录后投票
   发表时间:2007-11-06  
个人认为LZ过多的写了些IOC方面的知识,conversation倒涉及的不多
0 请登录后投票
   发表时间:2007-11-07  
3.1.4 无依赖扩展

对于第三方的软件,如果它不能适合应用的需求,往往需要对其中某项功能某个类进行扩展。扩展本身并非困难的事,只需要写一个类扩展原来的类,或者扩展已有的接口就可以了。困难的是,在绝大多数情形下,被扩展的类都会被该库内的其它类所依赖。一旦扩展了该类,那也就意味着也需要更改那些依赖它的类。

在全局域管理的模式下,类和类之间并不直接交流,所有的类都和容器或者框架进行交流,这样每个类就被重新还原成了独自的个体。如果一个第三方库是全局域管理的模式构成,就会很容易扩展,只需要更改需要扩展的类就可以了。

当然,因为现在的联系由"类-类"转换为了"框架-类",仍然需要告诉框架这个类作了改动。然而这是非常容易的。只需要把库里原来的那个类的名字加在被扩展了的类的上面,并告诉框架扩展之后的类优先于原先的类,就行了。

在Seam中,这也有两种做法,一是通过元注解,一是通过xml配置文件。
Xml配置在所有应用中都大同小异,这里就不说了。说一下元注解的方法:
比如,假如上面小节里的Parser(更改之前的Parser,是类不是接口)是库里的类,我无法对其作改动,但是我仍然需要扩展它使它改为对Xml文件解析而非原来的对Property文件进行解析,该怎么做呢?
@Name("parser")
@Install(precedence=15)
public class XMLParser extends Parser {
    private static final String CONFIG_FILE = "org/javaeye/resources/config.xml";  
    List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}  
}

这里我把XMLParser起成了和原来Parser一样的名字"parser",但是这样就有两个重名的类了。为了告诉容器XMLParser才是parser名字下真正需要的类,这里加入了一行:
@Install(precedence=15)

precedence的值的高低决定了容器以哪个类作为这个名字的类,高的优先于低的。
取值15是因为在Seam里,默认的库类的值是10,而默认的应用代码类的值是20,这里因为是介于库和应用代码之间,取值15。
通常这是有固定的常量来表示的,在Seam中,定义了若干这样表示优先级的常量:
BUILD_IN = 0;
FRAMEWORK = 10;
APPLICATION = 20;
DEPLOYMENT = 30;
MOCK = 40;
我们看到,我们永远可以通过指定更高级别的优先级来覆写Seam中的任意一个组件。而扩展后的类,可以被放在任意的位置,放在哪个包里都不重要,因为Seam是根据名字而非包全称的类名来查找组件的。
0 请登录后投票
   发表时间:2007-11-07  
3.2 全局域管理模型的构筑因素

前面提到过,全局域管理并非是web世界所独有的,它是一个OO问题,牵涉到对对象,内存管理的方式。在全局域管理的模型下,一个对象变得像它的类了,类变得像接口了,动态方法变得像静态方法了,应用由网状的类与类之间的关系,变成了伞状的中心-周边的容器-类之间的关系。前面一直在写Seam,是因为Seam首先大规模引入这样的模型,很多对模型本身的说明必须考虑Seam的实现方式。下面会舍弃Seam这个例子而考虑更抽象的内容,探讨一下对全局域管理模型的一些其它思考。

3.2.1 构筑因素

3.2.1.1 加载-销毁
3.2.1.2 类配置
3.2.1.3 容器连接点
3.2.1.4 域事件
3.2.1.5 预设域
0 请登录后投票
   发表时间:2007-11-07  
spring,又叫spring容器,叫做容器本身,就充当了对类的创建和管理,它虽然默认没有coversation的生命周期,但自己也可以自定义类似的生命周期叫spring自己管理
0 请登录后投票
   发表时间:2007-11-07  
感觉seam的优势在域模型建模上,backingbean和ejb同为一体,model和control合为一体,充血模型,设计思路上可以有更多的变化。。。
希望有人能结合seam,针对开发上的设计方案提出探讨
传统的ssh,3层架构action+service+dao,虽然到目前看来略显麻烦,但我觉得却是最保险,扩展性最好,能适用于几乎所有的扩展需求。。

代替这种架构,seam如何更好的做到?seam给的例子在设计上都过于简单,只是对seam能是实现什么功能上进行演示

要有最佳时间,要真的敢用,感觉还要有更多关于结合seam框架,对系统架构方面的探讨
0 请登录后投票
   发表时间:2007-11-07  
dingyuan 写道
感觉seam的优势在域模型建模上,backingbean和ejb同为一体,model和control合为一体,充血模型,设计思路上可以有更多的变化。。。
希望有人能结合seam,针对开发上的设计方案提出探讨
传统的ssh,3层架构action+service+dao,虽然到目前看来略显麻烦,但我觉得却是最保险,扩展性最好,能适用于几乎所有的扩展需求。。

代替这种架构,seam如何更好的做到?seam给的例子在设计上都过于简单,只是对seam能是实现什么功能上进行演示

要有最佳时间,要真的敢用,感觉还要有更多关于结合seam框架,对系统架构方面的探讨


看看Seam的wiki项目吧,是用Seam写的,准备用于生产的。Gnu LGPL license。

Seam和Spring的方式有非常大的不同,这些Seam文档里都写得很多,可以参考一下。
0 请登录后投票
   发表时间:2007-11-07  
Conversation也不是什么稀奇的事,Spring Webflow和Apache Shale里面的Dialog也都能做类似的事。

Seam确实做了很多事,但既然有人徒省事买现成的品牌机,也必然会有人根据自己的喜好买兼容机。

就Web层来讲,Facelet+A4J+Shale+Myfaces是比较舒服的解决方案,当然,你要做好心理准备,不啃透JSF的生命周期管理,状态管理和被设计的极其灵活的Phase机制就会让你云里雾里。

业务层来说,Seam的风格挺不错,简洁又不失灵活,不过先进的东西不一定流行,企业应用真正关心的事务处理EJB2可以干,持久层上也有太多选择,实在没有什么理由迁移到EJB3,而Web应用领域现在又是.NET和PHP之类动态语言的天下,更不需要JSF和EJB这样的东东。Seam实在是有一身好本领施展不开阿。
0 请登录后投票
   发表时间:2007-11-07  
zaya 写道
3.1.3 接口即类

在软件开发的时候,往往很难决定一项功能究竟是做成实实在在的类,还是做成接口以防以后的扩展或者替换。做成类得话能省去很多麻烦,但是会造成依赖它的类对该类的signature(类名,方法名等等)有绑死的依赖。如果被依赖类被非常多的类依赖得话,以后万一想作改动就会造成大面积的牵动。而如果对每个子功能都作成接口得话,则会大大增加系统的复杂度。

在以往应用的做法中,一般是只把预计以后可能变动的功能提出来被接口(或抽象类)封装,而对可能不会发生变动的功能就使用直接类的方式。这种方式是在过去的编程模式下比较好的一种折衷,但是它也有弊端。人的预计能力是有限的,很常见预计变动的功能最后没有变动,而预计不大可能发生变动的功能结果最后却发现需要替换。

在全局域管理下,有一种新的更方便的解决方法。对于代码:
@Name("config")
public class Config {
    @In Parser parser;
    private List<Module> modules;
    public void loadParser(){
        modules = parser.parser()
    }
    public String get(String moduleName, String configName) {
        //通过指定配置模块的名称和配置项的名称来查找某个配置的设定
    }
}

@Name("parser")
public class Parser {
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


这是一个简单的例子,Config类是整个应用的配置类,其它类可以通过它获取某项配置的内容。配置文件可能比较大,所以被分割成若干模块(Module),通过指定模块名和在模块内的配置名来查找某项配置的内容。Config类依赖于Parser类,通过Parser来把一个配置文件加载并分割成若干模块。

回到我们刚才的问题,这里我们把被依赖类Parser做成了一个类而非接口,那么假如以后我们想实现其它种类的Parser该怎么办呢?以往应用中我们不得不更改所有依赖类里面和被依赖类的衔接代码,但是在Seam里,这样的代码不需要,我们只需要改动被依赖类就可以了。
public interface Parser {
    List<Module> parser();
}

@Name("obsoleteParser")
public class PropertyParser implements Parser{
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}

@Name("parser")
public class XMLParser implements Parser{
    private static final String CONFIG_FILE = "org/javaeye/resources/config.xml";
    List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


我们看到,依赖类里面一行代码都没有被改动,我们就完成了所有替换。在实际应用中如果依赖类有非常多,那么这样的节省是巨大的。
我们也看到,在对被依赖类Parser进行变动的时候,我们把原来的类改成了接口,并把原来的实现放在了接口Parser的子类中。我们把原来的对property文件进行解析的实现命名为"obsoleteParser"表示这种实现已经作废,而把新的实现XMLParser命名为原来的名字parser。因为所有的依赖类都是根据"parser"这个名字来衔接的,对Parser类的改动不会影响依赖类的代码。

这里我们又看到了一个有趣的现象,在全局域管理的模式下,所有的类都变得像接口了。我们不用再考虑是否把某个功能抽象出来做成接口,直接用类实现就可以了。反正在以后把这些类重写成接口也是一件容易的事。

当然,接口的淡化并非是舍弃接口不用,这样搁置接口的能力,恰恰是接口本身的封装能力所决定了的。而且也并非所有的接口都可以这样使用。接口有两种,一种它的不同实现的替换是发生在编译时(compile time)的,就像上面的这个例子,是可以使用这样的方法的;一种它的不同实现的替换是发生在运行时(run time)的,则不可以使用这样的方法。

比如,如果一个应用有个User接口,它需要同时支持NormalUser和Administrator,这样的应用就不可能只写类不写接口。不到运行的时候,应用是无法知道究竟需要的是NormalUser 还是Administrator的。但是这样的应用是很好做决定是否使用接口的,通常这来自于功能需求而不是扩展与变动的需要。

这应该称为一半的依赖注入。因为利用注解,所以叫一半,通过维护一个名字空间(你文里所说的全局域管理)来实现。名字空间的维护不是原因,依赖注入才是有趣现象的本质。
0 请登录后投票
   发表时间:2007-11-07  
不知道该如何回复……

我想,这篇文章是写给多少有些Seam基础的人看的,并非是对Seam的介绍文章,这里的观点不来自于任何已有的参考资料,如果是已有的东西的话我就不会写了。所以在问问题之前,多少了解一些Seam会比较有的放矢一些。

或许之后我会写一篇介绍Seam的入门文章来,但是实际上这样的东西已经很多了,在别得地方也可以找得到。如果您完全不了解Seam,我建议您先多少读一些相关资料再来看本文,或许会好一些。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics