论坛首页 Java企业应用论坛

论面向组合子程序设计方法 之 微步毂纹生

浏览 87449 次
该帖已经被评为精华帖
作者 正文
   发表时间:2005-08-10  
swing 写道

那么我的另个问题是,你怎么来控制组合子的设计,以保证不会过度?因为一开始你如果只从细节上去考虑,可能会做很多的组合子以便将来应付变化,但是也许根本没有什么变化呢?比如,也许我要的LOG系统只是需要根据level能将需要的level输出到控制台就可以乐,其它什么写文件之类的根本不需要(让server来帮我们做这些事情)。这个时候会不会有点过?

组合子的优美在于它的简单。就象我和oz说的,一般的组合子就那么几下子:0, 1,顺序,分支。

从这个log的例子上说,只有nop, writer, sequence是不看需求闭着眼睛写出来的。(希望你觉得它们足够简单?)
这些组合子如此简单,如此基本,以至于不被重用到的机会很小(如果它们都不能被重用到,你的需求也许太小儿科,根本用不着组合子了);以至于,就算它们没有被重用,你浪费的体力也基本可以忽略。


剩下的,ignore, filter, 还是从需求来的。虽然没有需求,你也可以闭着眼睛弄出来,但是就像你说的,这样做不见得有用。

至于说控制台和写文件。老兄,看看代码。我用的是PrintWriter,本来就不关心这两者的区别,哪有什么过不过的?



swing 写道

顺带问的是:一个单表增删改的实现上,你会怎么发挥它的威力呢?

我的logger是有一个基本的Logger接口。没有基本接口来对概念建模,怎么组合呀?


假设数据库的操作是这样一个接口:

interface Action{
  Object execute(Context ctxt);;
}
interface Context{
  Connection getConnection();;
  Logger getLogger();;
}

返回值可以是Statement, ResultSet,List之类任何的东西。


那么,什么是0?
什么也不干,直接抛出exception
class ZeroAction implements Action{
  Object execute(Context ctxt);{
    throw new ActionException();;
  }
}

什么是1?
直接返回某个值

class ReturnAction implements Action{
  private final Object v;
  Object execute(Context ctxt);{
    return v;
  }
}


其它一些基本action:
log一个message
class LogAction implements Action{
  private final String msg;
  Object execute(Context ctxt);{
    ctxt.getLogger();.println(msg);;
  }
}


取得当前connection:
class GetConnectionAction implements Action{
  Object execute(Context ctxt);{
    return ctxt.getConnection();;
  }
}


然后是顺序,简单的顺序是直接顺序执行一堆action
class SequenceAction implements Action{
  private final Action[] actions;
  Object execute(Context ctxt);{
    if(actions.length==0);return null;
    for(int i=0; i<actions.length-1; i++);{
      actions[i].execute(ctxt);;
    }
    return actions[actions.length-1].execute(ctxt);;
  }
}

然后是复杂点的顺序,前一个action的执行结果决定下一个action,这里需要引入一个ActionBinder接口先:
interface ActionBinder{
  Action bind(Object v);;
}

class BoundAction implements Action{
  private final Action a1;
  private final ActionBinder binder;
  public Object execute(Context ctxt);{
    return binder.bind(a1.execute(ctxt););.execute(ctxt);;
  }
}


一个非常有用的map combinator,虽然它可以从bind演绎出来,但是因为太长用了,我们还是直接实现一下:
interface Map{
  Object map(Object v);;
}
class MapingAction implements Action{
  private final Action a;
  private Map m;
  public Object execute(Context ctxt);{
    return m.map(a.execute(ctxt););;
  }
}

这个组合子用来把一个action的返回值transform成另外一个对象。比如,一个action返回list,你可以用map把它变成一个返回array的action。



一般还会有一个把action在一个transaction里面运行的action。
class AtomAction implements Action{
  private final Action a;
  public Object execute(Context ctxt);{
    final Connection conn = ctxt.getConnection();;
    boolean ok = false;
    try{
      Object r = a.execute(ctxt);;
      ok = true;
      return r;
    }
    finally{
      if(ok); conn.commit();;
      else conn.rollack();;
    }
  }
}


其它还会有些try-catch之类的组合子。不过先到这。

下面再看看需求,往往需求会要求运行一个select, update, insert,一个select可以这样:
interface ResultSetTransformer{
  Object transform(ResultSet rset);;
}
class SelectAction implements Action{
  private final String qry;
  private final Object[] args;
  private final ResultSetTransformer trans;
  public Object execute(Context ctxt);{
    final Statement stmt = ctxt.getConnection.prepareStatement(qry);;
    try{
      for(int i=0; i<args.length; i++);{
        stmt.setValue(i, args[i]);;
      }
      final ResultSet rset = stmt.executeQuery();;
      try{
        return trans.transform(rset);;
      }
      finally{rset.close();;}
    }
    finally{stmt.close();;}
  }
}

还可以写一个调用executeUpdate()的来处理没有返回值得情况。


最终,使用这些组合子的时候,比如:
Action select_max = select("select max(id); from table1");;
ActionBinder insert_record = new ActionBinder();{
  public DbAction bind(Object id);{
    final Integer new_id = new Integer(((Integer);id);.intValue();+1);;
    return update("insert into table1(id, name); values(?, ?);",
      new Object[]{new_id, "tom"}
    );;
  }
};
DbAction new_record = atom(bind(select_max, insert_record););;

这个new_record就可以在一个transaction内部作select max,然后insert这种勾当。(效率低,我知道,一个例子嘛)


你这个问题特别好。我看看下面的章节能不能把它包括进去。
0 请登录后投票
   发表时间:2005-08-10  
charon 写道
ajoo 写道
那么怎么处理:“如果level>=x写入文件1,同时在屏幕上打印(不过不打印stack trace只打印getMessage()),而不管什么level,都写入一个通用log文件,对exception直接printStackTrace()。”?

这个需求,hehe,怎么说,实现起来非常简单。
这里仅演示SimpleLayout的情况(Log4j一共也没几种Layout,搞一圈也不花工夫)

package org.charon.log;

import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.SimpleLayout;

public class SimpleLayOutIgnoreException extends SimpleLayout {
    public SimpleLayOutIgnoreException(); {}
    public boolean ignoresThrowable(); {return false; }
}

然后定义一个log4j.properties:
#1 定义了两个输出端
log4j.rootLogger =INFO,A1,A2

#2 定义A1输出到控制器
log4j.appender.A1 = org.apache.log4j.ConsoleAppender
#3 定义A1的布局模式为PatternLayout
log4j.appender.A1.layout = org.charon.log.SimpleLayOutIgnoreException

#4 定义A2输出到文件
log4j.appender.A2 = org.apache.log4j.FileAppender
log4j.appender.A2.File = test.log
log4j.appender.A2.layout = org.apache.log4j.SimpleLayout

就可以演示了,很简单对不对?

哪里有区分printStackTrace/getMessage()的逻辑?没看到阿。你演示的好象是要不ignore要不print。

还有,我的printExecutionTrace()在你看来是个伪问题了?我根本就不应该要求在屏幕上只打印execution trace而在文件里面打印stack trace?
是这意思?如果做不到,就是需求错了?
那要是SQLException呢?如果我希望遇到SQLException的时候,对error code进行翻译,打印在屏幕上,而在文件里面打印stack trace,是不是也是一个不合理的要求?
还是我应该要求sun改写SQLException,在stack trace里面就包括翻译好的error code?





charon 写道

但是,我敢保证,这个东西不是什么实际的需求,只是为了表明某个特性而演示的一个东西。

可惜。在我的neptune里面,这就是一个实际的需求。我确实希望在屏幕和文件里面打印不同的东西。


charon 写道

这只是一个只需要在Layout类中添加一个属性就可以配置的功能(这样一来,就不需要前面的子类化了),但是这个演化了将近十年的工具却没有去走到这一步,只能说明这个需求至少现在还没有人把它当作一个实际的事情。

不同的应用有不同的领域。我的logger是用在build tool里面的。一个工具没有良好支持的功能是不是就是不合理的功能呢?我怀疑这个论点。

另外,你的比较并不公平。我演示的是怎么实现logger,而你演示的是怎么用一个现有的库。
公平的比较应该是把log4j的代码掰开,让我们看看log4j为了应对这么些灵活性付出了什么样的复杂性的代价。和基于co的方法比较一下。
然后看看我这信马由缰,几个小时写出来的简单的东西为什么能够逼近甚至也许超过这个十年时间演化出来的东西的功能和灵活性。(当然,我没有配置文件等的完备性,但是,我说过了,这些精装修在考虑系统框架的时候我认为是细枝末节)

就那么几个几乎任何人都看得懂的小组合子,随便组合组合就可以和log4j相提并论,难道不能说co这个方法论有那么点谈笑间,樯橹灰飞烟灭的能力?
0 请登录后投票
   发表时间:2005-08-10  
charon 写道
不过仔细想想,CO这个玩意儿还是很有点意思。等回睡觉的时候好好咀嚼一下,看看有什么东西可以吸收吸收。还是gigix说得比较直观。我觉得关键不是CO能够做什么,而是它不能够做到那些事情。只有知道这一点,心里才踏实点。ajoo把这个东西说得像#####一样,的表达能力太@!@#$了,要求向大家鞠躬!!!
就这个logging的例子,我觉得如果好好打磨,在使用接口上CO的做法和log4j能够基本持平,都通过配置文件较为简单的搞定,在内部结构上CO的做法能够更胜一筹,但是在效率上,我想比起log4j来可能会有问题,这也是log4j广泛传播的一个重要原因。还有一个,就是外围工具,现在log4j可是有很多的appender的,太多了。


效率问题确实一语种地。

java中如果应用co,必然是几层十几层的多态函数调用。所以应用在效率敏感的场合是不合适的。
我用co开发的jparsec parser combinator库,效率就上不去。

不过我怀疑logging这种东西这几层的函数调用开销有多么要紧。

要真考虑效率,还就得象我们可爱的c++拥趸说的一样,改用c++,用gp,魔板。不过生产力就更低下了。trade-off。

好在我这个logger只是给我的build tool自己用的。你说的这些问题在我这里都没有。而你不认可的灵活性需求在我这里却是实实在在的。
0 请登录后投票
   发表时间:2005-08-10  
今天想到一个比喻。


什么是“市场经济”?一个从古代乘坐时光机器来到现代的古人会不会问:你的所谓市场经济和我们那时候的村里人卖吃剩的猪肉有什么区别?还是说,不过是卖的人多了些,就是“市场经济”了?



一个以市场为整个社会主要运行机制并且驱动社会发展的经济是市场经济,村里面几个卖自家吃剩的猪肉的不是。很多卖猪肉的如果不能左右社会的组成和意识形态也不是。

一个以组合逻辑为核心的设计方法论是co,几个提供些额外功能的decorator类不是。很多decorator如果不能从演绎,组合,高阶逻辑的角度来构架发展系统,也不是。

这个比喻看上去有贬低oo的感觉,特此声明,非我本意。本人无意敲丧钟。这个世界并非除了黑就是白。
0 请登录后投票
   发表时间:2005-08-10  
ajoo 写道
哪里有区分printStackTrace/getMessage()的逻辑?没看到阿。你演示的好象是要不ignore要不print。

还有,我的printExecutionTrace()在你看来是个伪问题了?我根本就不应该要求在屏幕上只打印execution trace而在文件里面打印stack trace?
是这意思?如果做不到,就是需求错了?

这里的ignoreException函数是给appender用来判断layout有没有处理exception用的,如果layout对象返回false,则appender就不会再处理stacktrace,否则,则会把loginEvent中的statcktrace打印出来。
现在我的那个SimpleLayoutIgnoreException没有处理Exception,就是说只打印了message,但是ignoreException返回了false,使得appender也不会去打印stacktrace。(大多数layout实际上是不处理stacktrace的,而交由appender去搞定,但是message始终会打印出来)
所以在最终效果上,A1只会打印message,而A2则会打印出message+exception的stack trace。就这样完成功能.
所以无所谓做得到做不到。去做就肯定能做到,只不过在于log4j是不是把这个东西放到内核里面去,是通过配置做到还是通过扩展做到。

引用

那要是SQLException呢?如果我希望遇到SQLException的时候,对error code进行翻译,打印在屏幕上,而在文件里面打印stack trace,是不是也是一个不合理的要求?
还是我应该要求sun改写SQLException,在stack trace里面就包括翻译好的error code?

不太明白,SQLException被捕获的的时候,自然带有error_message,比如因为什么东西而被XX了,并不需要对error_code进行翻译才行。
而且,即便要翻译,实现一个ObjectRenderer接口就行了,在里面搞定翻译工作,然后输出应当输出的message。
因为stack trace的打印可以通过ignoreException开关控制,所以并没有你说得那个问题存在。

charon 写道

可惜。在我的neptune里面,这就是一个实际的需求。我确实希望在屏幕和文件里面打印不同的东西。
不同的应用有不同的领域。我的logger是用在build tool里面的。一个工具没有良好支持的功能是不是就是不合理的功能呢?我怀疑这个论点。

这个怎么说。我一直以为你在演示一个超强灵活的logging模块,不特定于某个实际场景。因为你自己也说了,这个东西基本与需求关系不大。

引用

另外,你的比较并不公平。我演示的是怎么实现logger,而你演示的是怎么用一个现有的库。
公平的比较应该是把log4j的代码掰开,让我们看看log4j为了应对这么些灵活性付出了什么样的复杂性的代价。和基于co的方法比较一下。

其实log4j的架构是很清晰的,核心的概念就logger,layout,appender,filter几个东西。只不过在组装的灵活性上,或者说它的出发点并不是通过链级组装来实现什么,而是通过母盘组装,即对象的属性来搞定东西,所以灵活性上相比要差一些。
log4j的核心代码就log4j包下面的20几个文件,而和这次讨论相关的,大概不到10个文件。但是功能就可以已经比较完善了。

引用

然后看看我这信马由缰,几个小时写出来的简单的东西为什么能够逼近甚至也许超过这个十年时间演化出来的东西的功能和灵活性。(当然,我没有配置文件等的完备性,但是,我说过了,这些精装修在考虑系统框架的时候我认为是细枝末节)
就那么几个几乎任何人都看得懂的小组合子,随便组合组合就可以和log4j相提并论,难道不能说co这个方法论有那么点谈笑间,樯橹灰飞烟灭的能力?

faint,你现在已经有的功能还是比较少的,很多在谈的都是"蓝图"。其实一个东西的核心功能大概只会耗用10-20%的代码,其余的代码,都是在完善/扩展某个方面或处理某些特定的东西。
如果CO也要做到这一步,复杂性上(可理解性)会比log4j差很多.
我感觉组合子的粒度太小,构造一个复杂的东西之后,如果缺乏很好的分层机制,会非常难以理解。组合系统怎么分级表述是个问题
0 请登录后投票
   发表时间:2005-08-10  
charon 写道
ajoo 写道
哪里有区分printStackTrace/getMessage()的逻辑?没看到阿。你演示的好象是要不ignore要不print。

还有,我的printExecutionTrace()在你看来是个伪问题了?我根本就不应该要求在屏幕上只打印execution trace而在文件里面打印stack trace?
是这意思?如果做不到,就是需求错了?

这里的ignoreException函数是给appender用来判断layout有没有处理exception用的,如果layout对象返回false,则appender就不会再处理stacktrace,否则,则会把loginEvent中的statcktrace打印出来。
现在我的那个SimpleLayoutIgnoreException没有处理Exception,就是说只打印了message,但是ignoreException返回了false,使得appender也不会去打印stacktrace。(大多数layout实际上是不处理stacktrace的,而交由appender去搞定,但是message始终会打印出来)
所以在最终效果上,A1只会打印message,而A2则会打印出message+exception的stack trace。就这样完成功能.
所以无所谓做得到做不到。去做就肯定能做到,只不过在于log4j是不是把这个东西放到内核里面去,是通过配置做到还是通过扩展做到。

不是很了解。
就是说,你缺省的必然打印getMessage,只是一个决定是否打印stack trace的问题?
但是这样好么?stack trace往往已经有了error message,如果不禁止stack trace,不是一个error message会打印两遍?

另外,可扩展必然可配置,可配置不一定可扩展。配置,不过是在也许很拙劣的设计外面套上一层漂亮的外壳。
如果我们讨论一个商店的经营水平,后勤服务,资金流通等内部事务,它的店面是否窗明几净说明不了太多问题。


charon 写道

引用

那要是SQLException呢?如果我希望遇到SQLException的时候,对error code进行翻译,打印在屏幕上,而在文件里面打印stack trace,是不是也是一个不合理的要求?
还是我应该要求sun改写SQLException,在stack trace里面就包括翻译好的error code?

不太明白,SQLException被捕获的的时候,自然带有error_message,比如因为什么东西而被XX了,并不需要对error_code进行翻译才行。
而且,即便要翻译,实现一个ObjectRenderer接口就行了,在里面搞定翻译工作,然后输出应当输出的message。
因为stack trace的打印可以通过ignoreException开关控制,所以并没有你说得那个问题存在。


我要说的就是,exception也是一个普通的java对象,自然可能存在对某一类exception的特殊处理。这没什么可以指责的。如果框架不能支持这个特殊处理,就是一个shame。
不过既然你可以实现一个ObjectRenderer来特殊处理某一个exception,那么和我的针对某个exception实现一个组合子也没什么不同了。

注意,这里我争论的不是co vs. oo了。也不是log4j vs. mylogging。而是要justify这个对个别exception特殊处理的需求。我认为这个需求是自然的,合理的,强制要求stack trace必须打印execution trace不一定就合理。
就象我说的,屏幕上只打印execution trace,这就要求必须特殊处理。



charon 写道

引用

可惜。在我的neptune里面,这就是一个实际的需求。我确实希望在屏幕和文件里面打印不同的东西。
不同的应用有不同的领域。我的logger是用在build tool里面的。一个工具没有良好支持的功能是不是就是不合理的功能呢?我怀疑这个论点。

这个怎么说。我一直以为你在演示一个超强灵活的logging模块,不特定于某个实际场景。因为你自己也说了,这个东西基本与需求关系不大。

我是说我的build tool决定了这个需求对我的项目是实际的,并不是空想。我不认同你的“一个功能log4j没有支持,证明它不实际”的论断。所以你这个回答不搭嘎。



charon 写道

引用

另外,你的比较并不公平。我演示的是怎么实现logger,而你演示的是怎么用一个现有的库。
公平的比较应该是把log4j的代码掰开,让我们看看log4j为了应对这么些灵活性付出了什么样的复杂性的代价。和基于co的方法比较一下。

其实log4j的架构是很清晰的,核心的概念就logger,layout,appender,filter几个东西。只不过在组装的灵活性上,或者说它的出发点并不是通过链级组装来实现什么,而是通过母盘组装,即对象的属性来搞定东西,所以灵活性上相比要差一些。
log4j的核心代码就log4j包下面的20几个文件,而和这次讨论相关的,大概不到10个文件。但是功能就可以已经比较完善了。


数文件个数有意义么?还是数代码行数好些。你可以数数组合子方法里面和这个讨论相关的代码行数,我估计不超过200行。

charon 写道

引用

然后看看我这信马由缰,几个小时写出来的简单的东西为什么能够逼近甚至也许超过这个十年时间演化出来的东西的功能和灵活性。(当然,我没有配置文件等的完备性,但是,我说过了,这些精装修在考虑系统框架的时候我认为是细枝末节)
就那么几个几乎任何人都看得懂的小组合子,随便组合组合就可以和log4j相提并论,难道不能说co这个方法论有那么点谈笑间,樯橹灰飞烟灭的能力?

faint,你现在已经有的功能还是比较少的,很多在谈的都是"蓝图"。其实一个东西的核心功能大概只会耗用10-20%的代码,其余的代码,都是在完善/扩展某个方面或处理某些特定的东西。
如果CO也要做到这一步,复杂性上(可理解性)会比log4j差很多.
我感觉组合子的粒度太小,构造一个复杂的东西之后,如果缺乏很好的分层机制,会非常难以理解。组合系统怎么分级表述是个问题

我们就比实现现在已有的东西log4j需要多少代码好了,不要比什么蓝图。比较那些细枝末节只会淹没主题。
我已经演示了用co可以怎样“轻松”地做一个logging系统的核心,你觉得自己做一个log4j的核心也会有这种举重若轻的感觉么?

可理解性上,高阶逻辑有时候确实有不容易理解的问题,对“聪明程度”要求比较高。大概算是一个缺陷吧。但是co往往只用来处理这10%的核心问题。外围的完善没有什么灵活性要求,拿oo做很好。(其实,光说可理解性,oo的多态可是比po的直接耦合更难理解的。抽象程度高了,可理解性往往就会下降的)

你说的“分级表述”,什么意思?能不能具体点?

我是觉得,可调试性是个更大的问题。效率也比较讨厌。
0 请登录后投票
   发表时间:2005-08-10  
ajoo 写道
swing 写道

那么我的另个问题是,你怎么来控制组合子的设计,以保证不会过度?因为一开始你如果只从细节上去考虑,可能会做很多的组合子以便将来应付变化,但是也许根本没有什么变化呢?比如,也许我要的LOG系统只是需要根据level能将需要的level输出到控制台就可以乐,其它什么写文件之类的根本不需要(让server来帮我们做这些事情)。这个时候会不会有点过?

组合子的优美在于它的简单。就象我和oz说的,一般的组合子就那么几下子:0, 1,顺序,分支。

从这个log的例子上说,只有nop, writer, sequence是不看需求闭着眼睛写出来的。(希望你觉得它们足够简单?)
这些组合子如此简单,如此基本,以至于不被重用到的机会很小(如果它们都不能被重用到,你的需求也许太小儿科,根本用不着组合子了);以至于,就算它们没有被重用,你浪费的体力也基本可以忽略。


剩下的,ignore, filter, 还是从需求来的。虽然没有需求,你也可以闭着眼睛弄出来,但是就像你说的,这样做不见得有用。

至于说控制台和写文件。老兄,看看代码。我用的是PrintWriter,本来就不关心这两者的区别,哪有什么过不过的?



swing 写道

顺带问的是:一个单表增删改的实现上,你会怎么发挥它的威力呢?

我的logger是有一个基本的Logger接口。没有基本接口来对概念建模,怎么组合呀?


假设数据库的操作是这样一个接口:

interface Action{
  Object execute(Context ctxt);;
}
interface Context{
  Connection getConnection();;
  Logger getLogger();;
}

返回值可以是Statement, ResultSet,List之类任何的东西。


那么,什么是0?
什么也不干,直接抛出exception
class ZeroAction implements Action{
  Object execute(Context ctxt);{
    throw new ActionException();;
  }
}

什么是1?
直接返回某个值

class ReturnAction implements Action{
  private final Object v;
  Object execute(Context ctxt);{
    return v;
  }
}


其它一些基本action:
log一个message
class LogAction implements Action{
  private final String msg;
  Object execute(Context ctxt);{
    ctxt.getLogger();.println(msg);;
  }
}


取得当前connection:
class GetConnectionAction implements Action{
  Object execute(Context ctxt);{
    return ctxt.getConnection();;
  }
}


然后是顺序,简单的顺序是直接顺序执行一堆action
class SequenceAction implements Action{
  private final Action[] actions;
  Object execute(Context ctxt);{
    if(actions.length==0);return null;
    for(int i=0; i<actions.length-1; i++);{
      actions[i].execute(ctxt);;
    }
    return actions[actions.length-1].execute(ctxt);;
  }
}

然后是复杂点的顺序,前一个action的执行结果决定下一个action,这里需要引入一个ActionBinder接口先:
interface ActionBinder{
  Action bind(Object v);;
}

class BoundAction implements Action{
  private final Action a1;
  private final ActionBinder binder;
  public Object execute(Context ctxt);{
    return binder.bind(a1.execute(ctxt););.execute(ctxt);;
  }
}


一个非常有用的map combinator,虽然它可以从bind演绎出来,但是因为太长用了,我们还是直接实现一下:
interface Map{
  Object map(Object v);;
}
class MapingAction implements Action{
  private final Action a;
  private Map m;
  public Object execute(Context ctxt);{
    return m.map(a.execute(ctxt););;
  }
}

这个组合子用来把一个action的返回值transform成另外一个对象。比如,一个action返回list,你可以用map把它变成一个返回array的action。



一般还会有一个把action在一个transaction里面运行的action。
class AtomAction implements Action{
  private final Action a;
  public Object execute(Context ctxt);{
    final Connection conn = ctxt.getConnection();;
    boolean ok = false;
    try{
      Object r = a.execute(ctxt);;
      ok = true;
      return r;
    }
    finally{
      if(ok); conn.commit();;
      else conn.rollack();;
    }
  }
}


其它还会有些try-catch之类的组合子。不过先到这。

下面再看看需求,往往需求会要求运行一个select, update, insert,一个select可以这样:
interface ResultSetTransformer{
  Object transform(ResultSet rset);;
}
class SelectAction implements Action{
  private final String qry;
  private final Object[] args;
  private final ResultSetTransformer trans;
  public Object execute(Context ctxt);{
    final Statement stmt = ctxt.getConnection.prepareStatement(qry);;
    try{
      for(int i=0; i<args.length; i++);{
        stmt.setValue(i, args[i]);;
      }
      final ResultSet rset = stmt.executeQuery();;
      try{
        return trans.transform(rset);;
      }
      finally{rset.close();;}
    }
    finally{stmt.close();;}
  }
}

还可以写一个调用executeUpdate()的来处理没有返回值得情况。


最终,使用这些组合子的时候,比如:
Action select_max = select("select max(id); from table1");;
ActionBinder insert_record = new ActionBinder();{
  public DbAction bind(Object id);{
    final Integer new_id = new Integer(((Integer);id);.intValue();+1);;
    return update("insert into table1(id, name); values(?, ?);",
      new Object[]{new_id, "tom"}
    );;
  }
};
DbAction new_record = atom(bind(select_max, insert_record););;

这个new_record就可以在一个transaction内部作select max,然后insert这种勾当。(效率低,我知道,一个例子嘛)


你这个问题特别好。我看看下面的章节能不能把它包括进去。


good
我想CO至少是一种不错的分析、设计的方法,当然,仅就分析而言目前你说的内容还有些苍白,希望能细化下去。
不过,就前面的讨论而言,我觉得你完全没有必要和OO争或者说把这个CO提升到和OO比较。那样做没什么具体的意义,因为从理论上将,OO也是同样可以做出这样的设计,只是过程可能不同罢了,优略很难说(唯手熟尔?!)。
我觉得你可以考虑像TDD一样的说法,呵呵,就是纯粹的分析、设计的方法或者说是演绎设计的方法,至于是不是能和OO比较,将来自有公论。

目前,我觉得主要还有2方面问题:
1、过度设计,希望能够提出比较合理的避免过度设计的方式或方法,实际上,我想之所以会有这样的疑问,可能主要还是你的分析方法仍然没有完全描述出来的原因;因为不知道,设计基本组合子的动力或者说原因在哪里,在何时停止继续生产组合子,还有基于需求如何分析设计那种基于组合的组合子等等。
2、上面也提到,组合过多时,难免层层嵌套;效率我倒是认为不是问题,因为一旦出现效率问题,我们仍然可以通过优化组合或者生产新的更有效率的组合子来解决;主要是能够通过合理的异常体系或者其它的方式方法来比较完整准确地反映程序地运行状态,这包括异常处理、调优或者调试等的。
比如异常,一旦出现异常如何通过合理的异常体系来完整地展现当前程序运行的状态,能够让人很快并准确地知道问题的原因为佳。
只要有足够的信息支持开发,我想没人会怪它嵌套太多。

等你出更多内容再继续吧。
0 请登录后投票
   发表时间:2005-08-10  
我也就举一个实际中经常遇到的log例子供大家一晒。我们需要临时在运行中同步记录几个不同对象中部分属性的值,用C++实现的代码我演示一下,请大家不要把注意力集中在语言名称上:

类声明如下,我们需要记录其中的field字段,注意这里可能都是简化了的:
struct A {
int field;
};

struct B {
double field;
};

struct C {
string field;
};

记录发生的事件可能是定时器,或者其他事件,我们这里简化定义如下:
boost::signal&lt;void()&gt; signal_log;

然后是类的实例化:

A a;
B b;
C c;

注意这里a, b, c在实际中不一定都在同一个层次上出现,比如c可能是d的一个成员,而d才和a, b处于同一个层次,这里我们就忽略掉这个区别,因为你们会看到,这个处理起来没什么不同。

记录log的代码:

...

signal_log.connect(
std::cout &lt;&lt; boost::lambda::var(a.field) &lt;&lt; ", " &lt;&lt; boost::lambda::var(b.field) &lt;&lt; ", " &lt;&lt; boost::lambda::var(c.field) &lt;&lt; '\n';
);

...

没有类也没有函数,如果觉得不好看,我们可以稍微修改一下。

声明一个DataLog类,但注意这个类我们不希望依赖上面几个类,实际上这些代码分布在不同的模块中,这样才能保证最终代码的整洁,另外可能的话,还可以复用(一部分):

class DataLog {
std::ostream& os;
public:
...

void log(int a, double b, const string& c)
{
os &lt;&lt; a &lt;&lt; ", " &lt;&lt; b &lt;&lt; ", " &lt;&lt; c &lt;&lt; '\n';
}

...
};

然后记录log的代码是:

signal_log.connect(
boost::bind(&DataLog::log
, DataLog(...)
, boost::ref(a.field)
, boost::ref(b.field)
, boost::ref(c.field)
)
);

如果发现代码中间有什么问题和错误的欢迎指出,或者在此基础上的新的需求的也可以提出,我会尽量予以答复。与主题无关的问题请到abp交流。
0 请登录后投票
   发表时间:2005-08-10  
[quote="ajoo]不是很了解。
就是说,你缺省的必然打印getMessage,只是一个决定是否打印stack trace的问题?
但是这样好么?stack trace往往已经有了error message,如果不禁止stack trace,不是一个error message会打印两遍?

不会,error_message和stack trace是分开的。不管怎么弄,error_message都需要打印,stacktrace可以选择。
引用

另外,可扩展必然可配置,可配置不一定可扩展。配置,不过是在也许很拙劣的设计外面套上一层漂亮的外壳。
如果我们讨论一个商店的经营水平,后勤服务,资金流通等内部事务,它的店面是否窗明几净说明不了太多问题。

当然,扩展只是在遵循它的那个logger,layout,appender,filter构架的前提下的,但是,本身这个架构对付logging这件事情,已经足够了。

引用

我是说我的build tool决定了这个需求对我的项目是实际的,并不是空想。我不认同你的“一个功能log4j没有支持,证明它不实际”的论断。所以你这个回答不搭嘎。

一个工具包不可能去做所有的事情,如果能够做到对常见的场景通过简单的方式就可以搞定,对于不常见的提供一些稍为负载一点的东西或者通过扩展来搞定,那就是很棒的了。
对于你的那个需求,做到只是举手之劳,关键在于为什么没去做。好歹log4j发展了将近十年,有一个庞大的用户群体,基本可以认为这个社区对于所谓的需求有一个深刻的认识。
而且,对于一个特殊的东西,如果需要扩展,CO并不能比其他的方式要更好一些,大家都是写代码。如果能够不写代码光写配置搞定,那可能就牛牛了。

引用

数文件个数有意义么?还是数代码行数好些。你可以数数组合子方法里面和这个讨论相关的代码行数,我估计不超过200行。

log4j核心的几个类还是非常干净的。同等功能即便用CO的方式实现,我估计代码行不会少。这几个核心的类,其质量在开源项目中算是很不错的了。
我觉得那种需求较为明确,能够通过精巧的设计来获得针对某个领域的架构的那种场景,CO并没有什么明显的优势。

引用

我已经演示了用co可以怎样“轻松”地做一个logging系统的核心,你觉得自己做一个log4j的核心也会有这种举重若轻的感觉么?

其实在看你这个东西的时候,我一直有一个担心,随着要解决东西的增加,组合的复杂度马上就会显现出来。因为我现在还没有看到降解复杂度的方法,也许你已经有想法了?

引用

你说的“分级表述”,什么意思?能不能具体点?
我是觉得,可调试性是个更大的问题。效率也比较讨厌。

其实CO的做法抽象上去和构建一个公理系统差不多,只不过用了一种渐进的方式。好比有一大堆各类零件和一些装配规则,要装配出脑子里面想象的东西,比较简单的时候还好办,稍微复杂一些的东西就会有问题。公理系统中的证明通常都比较难,组合的装配也不会太简单。采用OO的方式,存在类的静态结构,在理解上相对好一些,而采用CO的方式,除了原子以外,其他的都是动态构造的(如果也弄一些静态块,就有点失去意义),对于理解力绝对是一个考验。所以,肯定需要一个类似的方式来解决。而且,这里面有些东西是浮现出来的(emergence), 打个比方,我们用简单细粒度的几种构造快搭出一个积木人来,此时,各个构造快之间的接口是可以简单定义的。但是,把腿部和躯干连接起来的驳口,在事先是无法考虑到的。本身要对接的两个立面各自都由若干组合子构成,其实是组合子构成的构造的一个对接。
还有一个问题,是在渐进的过程中怎么确保正交性,很可能会出现同一件事情可以有多个方式来解决的情形,此时这个正交性很可能已经被破坏掉了,组合子和组合规则(其实也是组合子了)都会存在这个问题。不过,这个对于受过严格训练的人来说,并不是太大的挑战,但对于我这样的,很可能就跑飞了。前面有位哥们希望通过生产新的组合子来搞定可能出现的效率问题,我怎么看都像是要积极主动地破坏正交性。
0 请登录后投票
   发表时间:2005-08-10  
引用
1、过度设计,希望能够提出比较合理的避免过度设计的方式或方法,实际上,我想之所以会有这样的疑问,可能主要还是你的分析方法仍然没有完全描述出来的原因;因为不知道,设计基本组合子的动力或者说原因在哪里,在何时停止继续生产组合子,还有基于需求如何分析设计那种基于组合的组合子等等。

我倒是觉得组合子方法比oo更容易避免过渡设计。基本粒子都简单得不象话,而且数目也不多。根本不必担心过度。
而对其它的原子功能,还是要通过需求来发现的。比如那个增删改,如果没有需求,我怎么知道需要做select, insert, update?最多只能做几个顺序,分支而已。



引用
2、上面也提到,组合过多时,难免层层嵌套;效率我倒是认为不是问题,因为一旦出现效率问题,我们仍然可以通过优化组合或者生产新的更有效率的组合子来解决;主要是能够通过合理的异常体系或者其它的方式方法来比较完整准确地反映程序地运行状态,这包括异常处理、调优或者调试等的。
比如异常,一旦出现异常如何通过合理的异常体系来完整地展现当前程序运行的状态,能够让人很快并准确地知道问题的原因为佳。
只要有足够的信息支持开发,我想没人会怪它嵌套太多。

我对效率的反应:就别在异常敏感的地方用就是了。
但是,准确反映程序状态以方便调试确实是个麻烦。我最近开发的几个co的程序,jparsec仍然有不容易debug的问题(不过,你用parser generator比如javacc也一样不容易调试,所以还差强人意);
yan,neptune和jaskell我都额外增加了frame trace,execution trace和evaluation trace来方便查找错误。另外再通过给每个组合子命名来帮助定位错误。


说道co,我认为所谓xo就是说这个x能够作为一种主导整体程序设计思路和架构的思想。倒不是说一说co就必然要强调co比oo强。我自己用起来也是oo, co并用的。各有各自的适用场合而已。
0 请登录后投票
论坛首页 Java企业应用版

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