Hard Code Logic -> Static Type -> Dynamic Type Data

在一篇介绍Design Pattern的文章中,我写过这样的看法:Design Pattern的目标就是分离代码中的 变化部分 和 不变部分,我们尽量把 变化部分 单独抽取出来,以便达到 不变部分 的 最大限度的重用。
最大限度的重用,也是软件开发技术追求的目标。

下面通过一个典型的例子,来分析软件开发技术在这个方向上的发展。
假设我们有这样一段代码:

do a lot of things before…

switch ( type ){
case A:
do something.
case B:
do something;
case C:
do something;
}

do a lot of things after…

我们看到, 这里的逻辑分支是硬编码的(The logic branches are hard coded.)
这意味着,每增加一个type, 就要增加一个硬编码的逻辑分支。
这也意味着,这段代码永远没有办法成为一个可以扩展的公用函数库,达到更好的重用。每次增加type,只能在源代码级别上进行扩展。我们一旦把这段代码的编译结果,打包起来成为公用函数库,这段代码就失去了任何扩展性。

解决这个问题的第一个手法是OO多态。
do a lot of things before…
instance.doSomething();
do a lot of things after…

这里的instance是一个interface,具体的实现类从外部注入进来。我们可以传入A, B, C等实现类,这样就实现了上面的switch(type) 的 case A, case B, case C。同样,我们可以传入D,E,F等实现。实现switch(type) 的 case D, case E, case F。这样就达到了逻辑分支的可扩展性。
这种OO多态手法的核心思路是把代码中的逻辑分支 类型化,把变化点分离出来;通过不同的类型实现,就可以实现不同的变化点逻辑。

某些情况下,人们还有更灵活的需求。那么引入了第二种手法,动态解释。
do a lot of things before…
invokeService( serviceScript );
do a lot of things after…

这个里面的serviceScript可以看作一个字符串。
有可能是这样的一条简单调用语句:”serviceA.doSomething()”.
也有可能是完整一段script: “{this.a = getServiceA(); a.doSomething(); a.doMore();}”

动态解释手法,非常灵活强大。一点不利的情况是,只有运行起来的时候,才知道serviceScript里面是否把 a.doSomething() 错误的写成 a.doesSomething()。
评论
flyjie 2006-01-05
buaawhl 写道
age0 写道
buaawhl 写道

这只是一种说法。

你给的那个例子里面,
cannon.LoadShell( ArmourPiercingShell );
cannon.FireAt( Tank );

cannon.LoadShell( NormalShell );
cannon.FireAt( Truck );

重用的就是 cannon.LoadShell( ); cannon.FireAt( ); 的两个方法的实现部分。这两个实现部分里面调用了传进来的参数接口。
我把这种 实现部分 称为 template。


那么,稍微变化一下,还是不是 template 呢?


重用部分 = 不变部分。

给出的新类图中,核心不变部分,不属于template重用了。只是一种接口关系定义重用。

不过,
cannon.LoadShell( );
cannon.FireAt( );
howizer.LoadShell, FireAt
等方法实现部分,还是可以归为 template 重用。因为变化的部分是,参数接口的实现部分。


Template是通过OO中的多态来实现的,采用继承的方式实现算法的异构和代码重用!将通用算法封装在抽象基类中,并将不同的算法细节放到子类中实现!但继承的强制性约束关系也让Template模式有不足的地方!就是抽象类中除了继承的子类外。它的通用方法不能被与抽象类无关的其它类重用。因为后者不是继承自前者。我想Template应该和Strategy模式通过组合解决这个问题,就像spring的HibernateDaoSupport和JdbcDaoSupport就是一个模版. 通过组合hibernateTemplate或jdbcTemplate来实现对CRUD模版方法的调用。

[code:1] public final HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}

public final JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
[/code:1]
buaawhl 2006-01-05
age0 写道
buaawhl 写道

这只是一种说法。

你给的那个例子里面,
cannon.LoadShell( ArmourPiercingShell );
cannon.FireAt( Tank );

cannon.LoadShell( NormalShell );
cannon.FireAt( Truck );

重用的就是 cannon.LoadShell( ); cannon.FireAt( ); 的两个方法的实现部分。这两个实现部分里面调用了传进来的参数接口。
我把这种 实现部分 称为 template。


那么,稍微变化一下,还是不是 template 呢?


重用部分 = 不变部分。

给出的新类图中,核心不变部分,不属于template重用了。只是一种接口关系定义重用。

不过,
cannon.LoadShell( );
cannon.FireAt( );
howizer.LoadShell, FireAt
等方法实现部分,还是可以归为 template 重用。因为变化的部分是,参数接口的实现部分。
age0 2006-01-05
buaawhl 写道

这只是一种说法。

你给的那个例子里面,
annon.LoadShell( ArmourPiercingShell );
cannon.FireAt( Tank );

cannon.LoadShell( NormalShell );
cannon.FireAt( Truck );

重用的就是 cannon.LoadShell( ); cannon.FireAt( ); 的两个方法的实现部分。这两个实现部分里面调用了传进来的参数接口。
我把这种 实现部分 称为 template。


那么,稍微变化一下,还是不是 template 呢?
buaawhl 2006-01-04
age0 写道
buaawhl 写道

:D 没错。
多态的作用 就是 模板重用。



多态的作用,首先应该是类型或接口的重用,就以上面的cannon设计为例,如果是在Ruby这种动态语言里面,我们甚至可以取消Shell这个超类,只要ArmourPiercingShell 和NormalShell提供相同的Hit()方法,这个设计就可以运作,只是会失去编译期间的类型检查,并可能导致runtime error。至于OO的Class内置this指针所具备的携带额外信息的能力,则应当归功于封装的作用。


这只是一种说法。

你给的那个例子里面,
annon.LoadShell( ArmourPiercingShell );
cannon.FireAt( Tank );

cannon.LoadShell( NormalShell );
cannon.FireAt( Truck );

重用的就是 cannon.LoadShell( ); cannon.FireAt( ); 的两个方法的实现部分。这两个实现部分里面调用了传进来的参数接口。
我把这种 实现部分 称为 template。
age0 2006-01-04
buaawhl 写道

:D 没错。
多态的作用 就是 模板重用。



多态的作用,首先应该是类型或接口的重用,就以上面的cannon设计为例,如果是在Ruby这种动态语言里面,我们甚至可以取消Shell这个超类,只要ArmourPiercingShell 和NormalShell提供相同的Hit()方法,这个设计就可以运作,只是会失去编译期间的类型检查,并可能导致runtime error。至于OO的Class内置this指针所具备的携带额外信息的能力,则应当归功于封装的作用。
buaawhl 2006-01-04
gKarerM 写道
依据需要扩展的种类的不同,还可以特别的把visitor/strategy这种模式拿出来说一说。

楼主的例子相极了template模式中的情形,抽象的一般是实体。

动态解释手法在使用中还是很麻烦的,至少你需要一个parser,构造出相应的对象,再去解释执行。。


:D 没错。
多态的作用 就是 模板重用。

http://forum.javaeye.com/viewtopic.php?t=13954
引用

OOP/AD basic
本文是为了配合庄子的论文,做一些OO的基础知识普及工作。
当然,即使是OO基础知识,我也不一定有足够的资格来普及,主要目的还是为了相互学习,共同进步。因此,为了便于读者找出漏洞,进行批评和抨击,帮助我进步,我尽量使用朴实无华,简单易懂的语言。用咱老百姓自己的话,讲述老百姓自己的故事。
为了突出重点,加强效果,文中不免矫枉过正,提出一些片面极端的看法。
为了方便起见,我也把Procedure Oriented称为PO。

1.代码重用
评价一门语言的重要标准之一,就是代码重用程度。PO的代码重用主要有两种方式:(1) 模块重用 Module Reuse (2) 模板重用Template Reuse

模块重用。比如,一个 a 模块,b调用a,c也调用a。这时候,a就是重用的。这种情况比较简单,PO已经做得非常好了,OO这方面没有什么太大的超越。

模板重用。一个处理流程的整个步骤都是固定的,就是其中一些部分是变化的。比如,在那个Design Pattern帖子里面。
http://forum.javaeye.com/viewtopic.php?p=82944
那个排序的例子里面,sort算法是重用的,comparator是变化的。这个时候,sort就是一个模板template,comparator属于模板template中的变量。
OO在模板重用这个方面具有超越PO的语法优势。PO一般用Callback Function Pointer实现模板重用,而OO的Class内置支持this指针,具有了携带额外信息的能力。
gKarerM 2006-01-04
楼上的例子有点儿容易让人迷惑。。
LoadShell()和FireAt()直接接受超类就好了。。

楼上的图片看不了?直接贴一个过来吧。。
age0 2006-01-04
其实呢,多态这种东西,早就是工业设计用滥了的常用方法。

就以火炮为例。

cannon.LoadShell( ArmourPiercingShell );
cannon.FireAt( Tank );

cannon.LoadShell( NormalShell );
cannon.FireAt( Truck );


至于Dynamic Type,风险确实不小,搞不好一不小心把猫当炮弹给射出去了。
gKarerM 2006-01-03
依据需要扩展的种类的不同,还可以特别的把visitor/strategy这种模式拿出来说一说。

楼主的例子相极了template模式中的情形,抽象的一般是实体。

动态解释手法在使用中还是很麻烦的,至少你需要一个parser,构造出相应的对象,再去解释执行。。
buaawhl
搜索本博客
其他分类
存档
最新评论