- 浏览: 453905 次
-
最新评论
-
carlos23:
我想知道用WriterLogger后,里面的writer在外面 ...
论面向组合子程序设计方法 之 燃烧的荆棘 -
marsyoung:
讲的什么。。没看懂。例子太抽象。。
论面向组合子程序设计方法 之 创世纪 -
wanghhao:
优雅的方式!
论面向组合子程序设计方法 之 微步毂纹生 -
ykdsg:
有源码吗?
论面向组合子程序设计方法 之 monad -
咱不怕:
如果在一个函数中多次使用到这个不变temp对象,按照Marti ...
关于 Replace Temp With Query
场景一:
个人喜欢state-based的单元测试。(定义见mock is not stub)。
可是有些时候,比如在测试一些使用java.sql.Connection, ibatis SqlMapClient等接口的类的时候,明显写stub很不好。(1,接口很大,有很多不相干的东西。2,版本一旦变化,这些接口可能跟着变化,如果写stub的话,就意味着stub要跟着这些第三方接口变化)
于是,只好mock。只好interaction based。只好每回内部实现一变就死盯着一坨坨的expectAndReturn找不再有效的expectation。
场景二:
一个遗留系统用自己的连接池,使用ConnectionManager.checkIn(Connection)来释放连接(而不是Connection.close)
为了重构,希望能够重载Connection.close(),让它调用checkIn()。
跟场景一的提到的原因一样,不希望直接写一个MyConnection implements Connection。于是只好自己写dynamic proxy,形成如下的代码:
后面又发现这个变态的一流系统居然用自己的startMyTransaction, endMyTransaction(), rollbackMyTransaction()来搞事务处理!
于是又要继续判断method.getName().equals("startTransaction"), method.getName().equals("commitTransaction")等等等等。
dynamic proxy的代码难看的要死。
最近终于受不了了。愤然写了一个叫做酒窝的东西(dimple)。
所谓dimple,就是dynamic implementor的意思。我要用纯java语法来实现我想实现的方法(不是expectAndReturn,也不是if(method.getName().equals("method1"))),但是同时我又不愿意写“implements TheInterface”,以避免被迫处理那些我不关心的method。
用法如下:
场景一:
这样实现我的stub:
没有讨厌的expectAndReturn,不用担心parameter matcher。
场景二:
于是,对close(), startTransaction, commitTransaction(),我们都直接调用ConnectionManager, 而对其它的method则委托给realConn。
这个Implementor类是纯java代码,除了dynamic proxy没有用任何其它技术。(没有enhancement,aop之类的)
个人感觉还是挺有用的。大家点评一下?
不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
对么?
当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
哦,看了你的代码,想了一下,估计你的意思是说,runApi的逻辑是需要改变的参数的state的,而且外面的逻辑需要依赖于这个改变的state.
如果这样的话,确实只能这样了。
不过还想啰嗦多一些题外话, 另外针对这样的设计,我觉得是一种很奇怪或者危险的设计:
换成这样:
这样的话,通过明确语义的接口,逻辑至少要清晰很多 。 然后,把所有的map.put的逻辑都封装到一个方法里面,针对你提到的语义的危险,那么也可以在这class:A 这个 地方加以规范了 。
至少,不再为此想那么多tricky甚至古怪到精妙的方式了。
runApi需要的就是一个Map,一点trick也没有。它才不管这个state是在什么时候变化的呢。这也是dependency injection的好处,组件可以简单到极点。
tricky的是这个runApi的客户代码。这本来是个遗留系统,现在要使用runApi来重构一下。原来是代码到处都是直接调用PropFactory.getInstance().getProperty(),现在要改为依赖注射的方式,把Map注射进runApi。
因为无法确定这个遗留系统本来是否依赖于状态变化,或者什么时候状态会变化,(其实,我也不想关心太多这个系统的实现细节)最好的办法就是不做任何假设,原封不动地照搬原来的语义(也就是直接使用一个PropertyMap,而不是一个PropertyMap的clone)。
这既没有什么古怪,也没有什么精妙之处。只是重构的一个原则罢了。
不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
对么?
当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
哦,看了你的代码,想了一下,估计你的意思是说,runApi的逻辑是需要改变的参数的state的,而且外面的逻辑需要依赖于这个改变的state.
如果这样的话,确实只能这样了。
不过还想啰嗦多一些题外话, 另外针对这样的设计,我觉得是一种很奇怪或者危险的设计:
换成这样:
这样的话,通过明确语义的接口,逻辑至少要清晰很多 。 然后,把所有的map.put的逻辑都封装到一个方法里面,针对你提到的语义的危险,那么也可以在这class:A 这个 地方加以规范了 。
至少,不再为此想那么多tricky甚至古怪到精妙的方式了。
不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
对么?
当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
为什么不这么做呢?因为这样做的语义是不同的。
这个PropertyMap可能被注射进组件后才被populate。
一个简单化的例子:
所以不能copy,只能adapter
另外,dimple不是要替代easymock。
只是说,当前提是你并不想做mock testing,而仅仅是为了方便用easymock来创建stub的时候,用dimple直接创建stub可能更合适。
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?
为什么不这么做呢?因为这样做的语义是不同的。
这个PropertyMap可能被注射进组件后才被populate。
一个简单化的例子:
所以不能copy,只能adapter
另外,dimple不是要替代easymock。
只是说,当前提是你并不想做mock testing,而仅仅是为了方便用easymock来创建stub的时候,用dimple直接创建stub可能更合适。
dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?
你是说ResourceManager.closeConnection(conn)么?嗯,老实说我一点也看不出这种方法为什么可以不被禁止。
其实,这个问题之所以被提出,一个原因就是因为一个遗留系统使用了ConnectionManager.checkIn(Connection)的方法。而我要调用Ibatis,(Ibatis对ConnectionManager当然是一无所知),于是为了保证安全我才把一个禁止了close()的Connection对象传递给IBatis,以避免IBatis,或者IBatis的用户代码调用Connection.close()。其实所有的麻烦罪魁祸首就是这个遗留系统非要标新立异弄什么ConnectionManager.checkIn,而不是遵循标准,允许Connection.close()
dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?
Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap(); ...
return result; }
对于你的第一个帖子,确实会碰到这样的问题,除了用一个Adapter, 没有更好的办法解决了。 不过对于后面提到的要替代EasyMock,我不觉得Dimple有充分的优势。
dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?
你是说ResourceManager.closeConnection(conn)么?嗯,老实说我一点也看不出这种方法为什么可以不被禁止。
其实,这个问题之所以被提出,一个原因就是因为一个遗留系统使用了ConnectionManager.checkIn(Connection)的方法。而我要调用Ibatis,(Ibatis对ConnectionManager当然是一无所知),于是为了保证安全我才把一个禁止了close()的Connection对象传递给IBatis,以避免IBatis,或者IBatis的用户代码调用Connection.close()。其实所有的麻烦罪魁祸首就是这个遗留系统非要标新立异弄什么ConnectionManager.checkIn,而不是遵循标准,允许Connection.close()
哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。
不过,我仍然想多啰嗦几句。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。
那个,啥意思?你是说看不出相比于直接Utils.convertToMap有何优势?还是看不出用一个adapter有何优势?
另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。
我对这个出发点也提出疑义。 虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。
虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
统一的接口来open, close又是啥吗意思?俺咋糊涂了尼。
更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。
第二个疑问:
意思是说原来conn.close()---> resourceManager.closeConn(conn);
哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。
不过,我仍然想多啰嗦几句。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。
那个,啥意思?你是说看不出相比于直接Utils.convertToMap有何优势?还是看不出用一个adapter有何优势?
另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。
我对这个出发点也提出疑义。 虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。
虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
统一的接口来open, close又是啥吗意思?俺咋糊涂了尼。
哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。
不过,我仍然想多啰嗦几句。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。
另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。
我对这个出发点也提出疑义。 虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。
虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
这个例子不是测试阿。它的前提是:
一个模块设计成可以注射java.util.Map。这个设计是很自然的。
其他的普通客户代码都是直接用HashMap或者java.util.Properties之类的东西来注射。
单元测试代码也是用HashMap的。也没问题。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
如果是这样的话,那么ConvertToMap 的逻辑也是有必要冒烟测试到的。 所以,
那测试也就很自然的从PropertyMap准备测试数据进行测试了,这样的测试也没有必要用DImple来模拟 ConvertToMap的逻辑阿,我只需要在测试里面测试到就可以了。
我所能想到的 ”需要实现这些接口/抽象类的“ 充要条件:
1) Easymock模拟的代码很难写 ,或者代码比用Dimple作stub要多得多
2)人家提供的接口是一个类,而不是一个接口。
对于1)如果我们依赖人家接口的一两个method,我觉的EasyMock写起来难度不大。 如果我们的逻辑依赖人家接口的大部分API 。 确实,stub要来得好得多。 但是此时,我可能就需要在 自己写一个stubImpl和用Dimple实现一个Stub来权衡了,但是这个结果是在 ”我们的逻辑需要依赖很多人家接口的method“情况下发生的,这样一来,估计看看Dimple的代码,满篇也是实现的代码了吧,在这个前提下,优势我暂时看不出来。
对于2) ,因为不用接口带来如此的难题,stub似乎不得不提出来了。但是作为选择,也可以用一个Adapter接口来封装人家的类。对这个Adapter接口的处理又回到了1)
easymock倒也不算多重。而且它还有重构安全的特性。
区别仅仅在于stub vs. mock。如果需要做interaction-based test,当然是easymock,没得说。而如果要做state-based test,用easymock来做stub就有点别扭。
我的sample都很简单,已经写在confluence里了。当然也许有点生造出来的嫌疑。你有什么好的例子么?
这个例子不是测试阿。它的前提是:
一个模块设计成可以注射java.util.Map。这个设计是很自然的。
其他的普通客户代码都是直接用HashMap或者java.util.Properties之类的东西来注射。
单元测试代码也是用HashMap的。也没问题。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
它仅仅使用get()一个方法,但是因为种种原因,并没有使用最小接口(我认为这种设计是非常正常的),而是用了这个java.util.Map接口。
看了这个例子,我的看法:
如果在视线中 需要这么复杂的代码来做测试,那么就意味着个这个接口设计并不“正常”了,根据你提到的“仅仅用到了map.get()"的这个意思,那么接口应该这样设计:
这个例子我存在异议。 看着有点困惑。
如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?
个人喜欢state-based的单元测试。(定义见mock is not stub)。
可是有些时候,比如在测试一些使用java.sql.Connection, ibatis SqlMapClient等接口的类的时候,明显写stub很不好。(1,接口很大,有很多不相干的东西。2,版本一旦变化,这些接口可能跟着变化,如果写stub的话,就意味着stub要跟着这些第三方接口变化)
于是,只好mock。只好interaction based。只好每回内部实现一变就死盯着一坨坨的expectAndReturn找不再有效的expectation。
场景二:
一个遗留系统用自己的连接池,使用ConnectionManager.checkIn(Connection)来释放连接(而不是Connection.close)
为了重构,希望能够重载Connection.close(),让它调用checkIn()。
跟场景一的提到的原因一样,不希望直接写一个MyConnection implements Connection。于是只好自己写dynamic proxy,形成如下的代码:
private Connection conn; public Object invoke(Object proxy, Method method, Object[] args){ if((args==null || args.length==0) && method.getName().equals("close")){ ConnectionManager.checkIn((Connection)conn); } else { try { return method.invoke(conn, args); } catch(InvocationTargetException e){ throw e.getTargetException(); } } }
后面又发现这个变态的一流系统居然用自己的startMyTransaction, endMyTransaction(), rollbackMyTransaction()来搞事务处理!
于是又要继续判断method.getName().equals("startTransaction"), method.getName().equals("commitTransaction")等等等等。
dynamic proxy的代码难看的要死。
最近终于受不了了。愤然写了一个叫做酒窝的东西(dimple)。
所谓dimple,就是dynamic implementor的意思。我要用纯java语法来实现我想实现的方法(不是expectAndReturn,也不是if(method.getName().equals("method1"))),但是同时我又不愿意写“implements TheInterface”,以避免被迫处理那些我不关心的method。
用法如下:
场景一:
这样实现我的stub:
MyParameter myparam = ...; SqlMapClient client = (SqlMapClient) Implementor.proxy(SqlMapClient.class, new Object(){ public Object insert(String id, Object param) { assertEquals("test id", id); assertSame(myparam, param); return null; } }); assertNull(new SomeClassUsingSqlMapClient(client).runSomeInsert(myparam));
没有讨厌的expectAndReturn,不用担心parameter matcher。
场景二:
final Connection realConn = ...; Connection conn = (Connection) Implementor.proxy(Connection.class, new Object(){ public void close() { ConnectionManager.checkIn(realConn); } public void startTransaction(){ ConnectionManager.startMyTransaction(realConn); } public void commitTransaction(){ ConnectionManager.commitTransaction(realConn); } }, realConn);
于是,对close(), startTransaction, commitTransaction(),我们都直接调用ConnectionManager, 而对其它的method则委托给realConn。
这个Implementor类是纯java代码,除了dynamic proxy没有用任何其它技术。(没有enhancement,aop之类的)
个人感觉还是挺有用的。大家点评一下?
评论
91 楼
ajoo
2007-01-11
继续给酒窝寻找用途。
在robbin的domain model设计的帖子中,提到了第二种模型(rich domain model)的一些不足之处(贫血模型也有一样的毛病)。引用如下:
问题就在于这个充当business workflow facade的业务逻辑对象,它的变动是相当频繁的。业务逻辑对象通常都是无状态的、受事务控制的、Singleton类,我们可以考察一下业务逻辑对象都有哪几类业务逻辑方法:
第一类:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。
ItemManager之所以要代理这种类,目的有两个:向Web层提供统一的服务调用入口点和给持久化方法增加事务控制功能。这两点都很容易理解,你不能既给Web层程序员提供xxxManager,也给他提供xxxDao,所以你需要用xxxManager封装xxxDao,在这里,充当了一个简单代理功能;而事务控制也是持久化方法必须的,事务可能需要跨越多个DAO方法调用,所以必须放在业务逻辑层,而不能放在DAO层。
但是必须看到,对于一个典型的web应用来说,绝大多数的业务逻辑都是简单的CRUD逻辑,所以这种情况下,针对每个DAO方法,xxxManager都需要提供一个对应的封装方法,这不但是非常枯燥的,也是令人感觉非常不好的。
第二类:domain logic的方法代理。就是上面例子中placeBid方法。虽然Item已经有了placeBid方法,但是ItemManager仍然需要封装一下Item的placeBid,然后再提供一个简单封装之后的代理方法。
这和第一种情况类似,其原因也一样,也是为了给Web层提供一个统一的服务调用入口点和给隐式的持久化动作提供事务控制。
同样,和第一种情况一样,针对每个domain logic方法,xxxManager都需要提供一个对应的封装方法,同样是枯燥的,令人不爽的。
第三类:需要多个domain object和DAO参与协作的business workflow。这种情况是业务逻辑对象真正应该完成的职责。
在这个简单的例子中,没有涉及到这种情况,不过大家都可以想像的出来这种应用场景,因此不必举例说明了。
通过上面的分析可以看出,只有第三类业务逻辑方法才是业务逻辑对象真正应该承担的职责,而前两类业务逻辑方法都是“无奈之举”,不得不为之的事情,不但枯燥,而且令人沮丧。
原文见:http://www.iteye.com/topic/11712
引用的这段话主要是说,在下面这个代码中,ItemManager必须要对loadItemById(), findAll()这两个方法进行委托:
例子中只有两个方法,还没什么,但是如果有10个呢?100个呢?这个委托就烦死人了。
我无意在此继续炒哪种domain model更好,我要做的,只是show一下酒窝可以用来解决robbin提出的这个问题的。
怎么解决呢,很简单,只要保证ItemManager里面的方法和被委托的方法签名一致就好了。最容易达到这个目的的办法就是弄一个额外的package private interface出来。这个接口的唯一目的是让ItemDao和ItemManager共同继承一些签名:
然后,让ItemDao继承AboutItem,再给ItemManager提取出一个接口出来:
到此为止呢,仅仅是声明一些接口,没有任何实现上的动作。
下面用dimple来实现ItemManagerImpl对ItemDao的委托:
这样,robbin提出的被迫写很多委托的问题就不存在了。也就不令人沮丧了。
在robbin的domain model设计的帖子中,提到了第二种模型(rich domain model)的一些不足之处(贫血模型也有一样的毛病)。引用如下:
引用
问题就在于这个充当business workflow facade的业务逻辑对象,它的变动是相当频繁的。业务逻辑对象通常都是无状态的、受事务控制的、Singleton类,我们可以考察一下业务逻辑对象都有哪几类业务逻辑方法:
第一类:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。
ItemManager之所以要代理这种类,目的有两个:向Web层提供统一的服务调用入口点和给持久化方法增加事务控制功能。这两点都很容易理解,你不能既给Web层程序员提供xxxManager,也给他提供xxxDao,所以你需要用xxxManager封装xxxDao,在这里,充当了一个简单代理功能;而事务控制也是持久化方法必须的,事务可能需要跨越多个DAO方法调用,所以必须放在业务逻辑层,而不能放在DAO层。
但是必须看到,对于一个典型的web应用来说,绝大多数的业务逻辑都是简单的CRUD逻辑,所以这种情况下,针对每个DAO方法,xxxManager都需要提供一个对应的封装方法,这不但是非常枯燥的,也是令人感觉非常不好的。
第二类:domain logic的方法代理。就是上面例子中placeBid方法。虽然Item已经有了placeBid方法,但是ItemManager仍然需要封装一下Item的placeBid,然后再提供一个简单封装之后的代理方法。
这和第一种情况类似,其原因也一样,也是为了给Web层提供一个统一的服务调用入口点和给隐式的持久化动作提供事务控制。
同样,和第一种情况一样,针对每个domain logic方法,xxxManager都需要提供一个对应的封装方法,同样是枯燥的,令人不爽的。
第三类:需要多个domain object和DAO参与协作的business workflow。这种情况是业务逻辑对象真正应该完成的职责。
在这个简单的例子中,没有涉及到这种情况,不过大家都可以想像的出来这种应用场景,因此不必举例说明了。
通过上面的分析可以看出,只有第三类业务逻辑方法才是业务逻辑对象真正应该承担的职责,而前两类业务逻辑方法都是“无奈之举”,不得不为之的事情,不但枯燥,而且令人沮丧。
原文见:http://www.iteye.com/topic/11712
引用的这段话主要是说,在下面这个代码中,ItemManager必须要对loadItemById(), findAll()这两个方法进行委托:
public interface ItemDao { public Item getItemById(Long id); public Collection findAll(); public void updateItem(Item item); } public class ItemManager { private ItemDao itemDao; public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao;} public Bid loadItemById(Long id) { itemDao.loadItemById(id); } public Collection listAllItems() { return itemDao.findAll(); } public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws BusinessException { item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid); itemDao.update(item); // 必须显式的调用DAO,保持持久化 } }
例子中只有两个方法,还没什么,但是如果有10个呢?100个呢?这个委托就烦死人了。
我无意在此继续炒哪种domain model更好,我要做的,只是show一下酒窝可以用来解决robbin提出的这个问题的。
怎么解决呢,很简单,只要保证ItemManager里面的方法和被委托的方法签名一致就好了。最容易达到这个目的的办法就是弄一个额外的package private interface出来。这个接口的唯一目的是让ItemDao和ItemManager共同继承一些签名:
interface AboutItem { Item getItemById(Long id); Collection findAll(); }
然后,让ItemDao继承AboutItem,再给ItemManager提取出一个接口出来:
public interface ItemDao extends AboutItem { void updateItem(Item item); } public interface ItemManager extends AboutItem { Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws BusinessException; }
到此为止呢,仅仅是声明一些接口,没有任何实现上的动作。
下面用dimple来实现ItemManagerImpl对ItemDao的委托:
public class ItemManagerImpl { private ItemDao itemDao; public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws BusinessException { item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid); itemDao.update(item); // 必须显式的调用DAO,保持持久化 } } ItemDao dao = ...; ItemManagerImpl impl = ...; ItemManager manager = (ItemManager)Implementor.proxy(ItemManager.class, impl, itemDao);
这样,robbin提出的被迫写很多委托的问题就不存在了。也就不令人沮丧了。
90 楼
ajoo
2007-01-08
codehaus上的用dimple进行ibatis integration的介绍
http://dimple.codehaus.org/Integrate+IBatis+With+Legacy+System
http://dimple.codehaus.org/Integrate+IBatis+With+Legacy+System
89 楼
ajoo
2007-01-07
关于那个Connection的例子,实际的情况(简化了的)是这样:
1。遗留系统用ConnectionManager.checkOut()和ConnectionManager.checkIn(Connection)这两个静态方法来创建和释放连接。(看到了吧?什么ConnectionManager, ResourceManager之类的方法都是害群之马!)
2。现在需要使用iBatis来实现一部分持久层。但是需要仍然遵循遗留系统的ConnectionManager方式。很明显,ibatis对我们自制的ConnectionManager一无所知,它只知道调用标准的Connection.close()来关闭连接。
解决方案是给ibatis配置一个DataSourceFactory类。要配置DataSourceFactory,需要提供一个特制的DataSource实现和Connection实现,让DataSource.getConnection()调用ConnectionManager.checkOut(),把ConnectionManager返回的realConnection封装成另外一个Connection实例,这个实例在被调用Connection.close()的时候,实际调用ConnectionManager.checkIn(realConnection)。
伪代码实现如下:
明显,上面的代码编译不过,因为我们没有实现所有的Connection和DataSource接口定义的方法。
传统的解决方法是,根据我们上面的注释,用Eclipse的自动代码生成来把所有其它的方法搞定。这样做的问题除了繁琐之外,最大的麻烦是versioning。也许你生成的代码是针对jdbc 3.0的,那么它放在jdbc 4.0下,将不能编译。
使用dimple,就不会有这个问题:
上面的代码里面,我们只实现了关心的方法,其它的都用dimple给自动搞定了。这个代码不管放到哪个jdbc版本下都工作(呵呵,当然得支持DataSource才行了)
1。遗留系统用ConnectionManager.checkOut()和ConnectionManager.checkIn(Connection)这两个静态方法来创建和释放连接。(看到了吧?什么ConnectionManager, ResourceManager之类的方法都是害群之马!)
2。现在需要使用iBatis来实现一部分持久层。但是需要仍然遵循遗留系统的ConnectionManager方式。很明显,ibatis对我们自制的ConnectionManager一无所知,它只知道调用标准的Connection.close()来关闭连接。
解决方案是给ibatis配置一个DataSourceFactory类。要配置DataSourceFactory,需要提供一个特制的DataSource实现和Connection实现,让DataSource.getConnection()调用ConnectionManager.checkOut(),把ConnectionManager返回的realConnection封装成另外一个Connection实例,这个实例在被调用Connection.close()的时候,实际调用ConnectionManager.checkIn(realConnection)。
伪代码实现如下:
class LegacyConnection implements Connection { private final Connection realConnection; private boolean closed; public void close(){ if(closed) return; ConnectionManager.checkIn(realConnection); closed = true; } public boolean isClosed() { return closed; } //所有其它的方法都委托给realConnection。 } class LegacyDataSource implements DataSource { public Connection getConnection() { return new LegacyConnection(ConnectionManager.checkOut()); } //所有其它的方法都不需要实现。 } public class LegacyDataSourceFactory implements DataSourceFactory { public DataSource getDataSource() { return new LegacyDataSource(); } }
明显,上面的代码编译不过,因为我们没有实现所有的Connection和DataSource接口定义的方法。
传统的解决方法是,根据我们上面的注释,用Eclipse的自动代码生成来把所有其它的方法搞定。这样做的问题除了繁琐之外,最大的麻烦是versioning。也许你生成的代码是针对jdbc 3.0的,那么它放在jdbc 4.0下,将不能编译。
使用dimple,就不会有这个问题:
class LegacyConnection /*implements Connection*/ { private final Connection realConnection; private boolean closed; public void close(){ if(closed) return; ConnectionManager.checkIn(realConnection); closed = true; } public boolean isClosed() { return closed; } } class LegacyDataSource /*implements DataSource*/ { public Connection getConnection() { final Connection realConnection = ConnectionManager.checkOut(); return Implementor.proxy(Connection.class, new LegacyConnection(realConnection), realConnection); } } public class LegacyDataSourceFactory implements DataSourceFactory { public DataSource getDataSource() { return Implementor.proxy(DataSource.class, new LegacyDataSource()); } }
上面的代码里面,我们只实现了关心的方法,其它的都用dimple给自动搞定了。这个代码不管放到哪个jdbc版本下都工作(呵呵,当然得支持DataSource才行了)
88 楼
ajoo
2007-01-05
firebody 写道
ajoo 写道
引用
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?
不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
Map someMap = new HashMap(); MyClass mc = new MyClass(someMap); someMap.put(...); mc.doSomething();
Map someMap = new HashMap(); MyClass mc = new MyClass(cloneMap(someMap)); someMap.put(...); mc.doSomething();
对么?
当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
哦,看了你的代码,想了一下,估计你的意思是说,runApi的逻辑是需要改变的参数的state的,而且外面的逻辑需要依赖于这个改变的state.
如果这样的话,确实只能这样了。
不过还想啰嗦多一些题外话, 另外针对这样的设计,我觉得是一种很奇怪或者危险的设计:
a.getMap().put(..) ,a.getList().add , 或者更甚的,就像你举的例子,直接把Map,List当作参数传来传去,在每个逻辑里面修改map/list的状态。
换成这样:
class a{ Map getMap() {return collections.unmodifialbale(map) } ; void addMapMember(key,value) ,..... }
这样的话,通过明确语义的接口,逻辑至少要清晰很多 。 然后,把所有的map.put的逻辑都封装到一个方法里面,针对你提到的语义的危险,那么也可以在这class:A 这个 地方加以规范了 。
至少,不再为此想那么多tricky甚至古怪到精妙的方式了。
runApi需要的就是一个Map,一点trick也没有。它才不管这个state是在什么时候变化的呢。这也是dependency injection的好处,组件可以简单到极点。
tricky的是这个runApi的客户代码。这本来是个遗留系统,现在要使用runApi来重构一下。原来是代码到处都是直接调用PropFactory.getInstance().getProperty(),现在要改为依赖注射的方式,把Map注射进runApi。
因为无法确定这个遗留系统本来是否依赖于状态变化,或者什么时候状态会变化,(其实,我也不想关心太多这个系统的实现细节)最好的办法就是不做任何假设,原封不动地照搬原来的语义(也就是直接使用一个PropertyMap,而不是一个PropertyMap的clone)。
这既没有什么古怪,也没有什么精妙之处。只是重构的一个原则罢了。
87 楼
firebody
2007-01-05
ajoo 写道
引用
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?
不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
Map someMap = new HashMap(); MyClass mc = new MyClass(someMap); someMap.put(...); mc.doSomething();
Map someMap = new HashMap(); MyClass mc = new MyClass(cloneMap(someMap)); someMap.put(...); mc.doSomething();
对么?
当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
哦,看了你的代码,想了一下,估计你的意思是说,runApi的逻辑是需要改变的参数的state的,而且外面的逻辑需要依赖于这个改变的state.
如果这样的话,确实只能这样了。
不过还想啰嗦多一些题外话, 另外针对这样的设计,我觉得是一种很奇怪或者危险的设计:
a.getMap().put(..) ,a.getList().add , 或者更甚的,就像你举的例子,直接把Map,List当作参数传来传去,在每个逻辑里面修改map/list的状态。
换成这样:
class a{ Map getMap() {return collections.unmodifialbale(map) } ; void addMapMember(key,value) ,..... }
这样的话,通过明确语义的接口,逻辑至少要清晰很多 。 然后,把所有的map.put的逻辑都封装到一个方法里面,针对你提到的语义的危险,那么也可以在这class:A 这个 地方加以规范了 。
至少,不再为此想那么多tricky甚至古怪到精妙的方式了。
86 楼
ajoo
2007-01-05
引用
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?
不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
Map someMap = new HashMap(); MyClass mc = new MyClass(someMap); someMap.put(...); mc.doSomething();
Map someMap = new HashMap(); MyClass mc = new MyClass(cloneMap(someMap)); someMap.put(...); mc.doSomething();
对么?
当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
85 楼
firebody
2007-01-05
ajoo 写道
Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap(); ... return result; }
为什么不这么做呢?因为这样做的语义是不同的。
这个PropertyMap可能被注射进组件后才被populate。
一个简单化的例子:
PropertyMapImpl pmap = new PropertyMapImpl(); MyClass mc = new MyClass(convertToMap(pmap)); pmap.addProperty(...); mc.doSomething();
所以不能copy,只能adapter
另外,dimple不是要替代easymock。
只是说,当前提是你并不想做mock testing,而仅仅是为了方便用easymock来创建stub的时候,用dimple直接创建stub可能更合适。
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?
84 楼
ajoo
2007-01-05
Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap(); ... return result; }
为什么不这么做呢?因为这样做的语义是不同的。
这个PropertyMap可能被注射进组件后才被populate。
一个简单化的例子:
PropertyMapImpl pmap = new PropertyMapImpl(); MyClass mc = new MyClass(convertToMap(pmap)); pmap.addProperty(...); mc.doSomething();
所以不能copy,只能adapter
另外,dimple不是要替代easymock。
只是说,当前提是你并不想做mock testing,而仅仅是为了方便用easymock来创建stub的时候,用dimple直接创建stub可能更合适。
83 楼
firebody
2007-01-05
ajoo 写道
引用
更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。
第二个疑问:
意思是说原来conn.close()---> resourceManager.closeConn(conn);
是说 Dimple相比于直接Utils.convertToMap有何优势 。
第二个疑问:
意思是说原来conn.close()---> resourceManager.closeConn(conn);
dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?
你是说ResourceManager.closeConnection(conn)么?嗯,老实说我一点也看不出这种方法为什么可以不被禁止。
其实,这个问题之所以被提出,一个原因就是因为一个遗留系统使用了ConnectionManager.checkIn(Connection)的方法。而我要调用Ibatis,(Ibatis对ConnectionManager当然是一无所知),于是为了保证安全我才把一个禁止了close()的Connection对象传递给IBatis,以避免IBatis,或者IBatis的用户代码调用Connection.close()。其实所有的麻烦罪魁祸首就是这个遗留系统非要标新立异弄什么ConnectionManager.checkIn,而不是遵循标准,允许Connection.close()
dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?
Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap(); ...
return result; }
对于你的第一个帖子,确实会碰到这样的问题,除了用一个Adapter, 没有更好的办法解决了。 不过对于后面提到的要替代EasyMock,我不觉得Dimple有充分的优势。
82 楼
ajoo
2007-01-05
引用
更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。
第二个疑问:
意思是说原来conn.close()---> resourceManager.closeConn(conn);
是说 Dimple相比于直接Utils.convertToMap有何优势 。
第二个疑问:
意思是说原来conn.close()---> resourceManager.closeConn(conn);
dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?
你是说ResourceManager.closeConnection(conn)么?嗯,老实说我一点也看不出这种方法为什么可以不被禁止。
其实,这个问题之所以被提出,一个原因就是因为一个遗留系统使用了ConnectionManager.checkIn(Connection)的方法。而我要调用Ibatis,(Ibatis对ConnectionManager当然是一无所知),于是为了保证安全我才把一个禁止了close()的Connection对象传递给IBatis,以避免IBatis,或者IBatis的用户代码调用Connection.close()。其实所有的麻烦罪魁祸首就是这个遗留系统非要标新立异弄什么ConnectionManager.checkIn,而不是遵循标准,允许Connection.close()
81 楼
firebody
2007-01-05
ajoo 写道
firebody 写道
ajoo 写道
没有看到我说的“不是测试的问题”么?
这里面dimple是用在production代码里面的。测试代码不用dimple。
这里面dimple是用在production代码里面的。测试代码不用dimple。
哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。
不过,我仍然想多啰嗦几句。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。
那个,啥意思?你是说看不出相比于直接Utils.convertToMap有何优势?还是看不出用一个adapter有何优势?
firebody 写道
另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。
我对这个出发点也提出疑义。 虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。
虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
统一的接口来open, close又是啥吗意思?俺咋糊涂了尼。
更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。
第二个疑问:
意思是说原来conn.close()---> resourceManager.closeConn(conn);
80 楼
ajoo
2007-01-05
firebody 写道
ajoo 写道
没有看到我说的“不是测试的问题”么?
这里面dimple是用在production代码里面的。测试代码不用dimple。
这里面dimple是用在production代码里面的。测试代码不用dimple。
哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。
不过,我仍然想多啰嗦几句。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。
那个,啥意思?你是说看不出相比于直接Utils.convertToMap有何优势?还是看不出用一个adapter有何优势?
firebody 写道
另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。
我对这个出发点也提出疑义。 虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。
虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
统一的接口来open, close又是啥吗意思?俺咋糊涂了尼。
79 楼
firebody
2007-01-05
ajoo 写道
没有看到我说的“不是测试的问题”么?
这里面dimple是用在production代码里面的。测试代码不用dimple。
这里面dimple是用在production代码里面的。测试代码不用dimple。
哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。
不过,我仍然想多啰嗦几句。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。
另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。
我对这个出发点也提出疑义。 虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。
虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
78 楼
ajoo
2007-01-05
没有看到我说的“不是测试的问题”么?
这里面dimple是用在production代码里面的。测试代码不用dimple。
这里面dimple是用在production代码里面的。测试代码不用dimple。
77 楼
firebody
2007-01-05
ajoo 写道
引用
这个例子我存在异议。 看着有点困惑。
如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?
如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?
这个例子不是测试阿。它的前提是:
一个模块设计成可以注射java.util.Map。这个设计是很自然的。
其他的普通客户代码都是直接用HashMap或者java.util.Properties之类的东西来注射。
单元测试代码也是用HashMap的。也没问题。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
如果是这样的话,那么ConvertToMap 的逻辑也是有必要冒烟测试到的。 所以,
那测试也就很自然的从PropertyMap准备测试数据进行测试了,这样的测试也没有必要用DImple来模拟 ConvertToMap的逻辑阿,我只需要在测试里面测试到就可以了。
76 楼
firebody
2007-01-05
taowen 写道
呵呵,有些系统提供的接口我们是重构不了的啊。所以实现这些接口就是价值所在了。
我所能想到的 ”需要实现这些接口/抽象类的“ 充要条件:
1) Easymock模拟的代码很难写 ,或者代码比用Dimple作stub要多得多
2)人家提供的接口是一个类,而不是一个接口。
对于1)如果我们依赖人家接口的一两个method,我觉的EasyMock写起来难度不大。 如果我们的逻辑依赖人家接口的大部分API 。 确实,stub要来得好得多。 但是此时,我可能就需要在 自己写一个stubImpl和用Dimple实现一个Stub来权衡了,但是这个结果是在 ”我们的逻辑需要依赖很多人家接口的method“情况下发生的,这样一来,估计看看Dimple的代码,满篇也是实现的代码了吧,在这个前提下,优势我暂时看不出来。
对于2) ,因为不用接口带来如此的难题,stub似乎不得不提出来了。但是作为选择,也可以用一个Adapter接口来封装人家的类。对这个Adapter接口的处理又回到了1)
75 楼
ajoo
2007-01-05
taowen 写道
看了一下发型包,已经0.4啦?现在有sample了么?有没有想好写一些什么经典应用场景呢?比如在做stub的情况下,和easymock做一个对比。至少可以告诉大家,在只要stub的情况下,犯不着搬出mock框架这么重的东西来。
easymock倒也不算多重。而且它还有重构安全的特性。
区别仅仅在于stub vs. mock。如果需要做interaction-based test,当然是easymock,没得说。而如果要做state-based test,用easymock来做stub就有点别扭。
我的sample都很简单,已经写在confluence里了。当然也许有点生造出来的嫌疑。你有什么好的例子么?
74 楼
ajoo
2007-01-05
引用
这个例子我存在异议。 看着有点困惑。
如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?
如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?
这个例子不是测试阿。它的前提是:
一个模块设计成可以注射java.util.Map。这个设计是很自然的。
其他的普通客户代码都是直接用HashMap或者java.util.Properties之类的东西来注射。
单元测试代码也是用HashMap的。也没问题。
问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
73 楼
taowen
2007-01-05
呵呵,有些系统提供的接口我们是重构不了的啊。所以实现这些接口就是价值所在了。
72 楼
firebody
2007-01-05
ajoo 写道
再举一个adapter的例子。
不知道大家遇到过这样的情况没有。有一个api,有这个接口签名:
它仅仅使用get()一个方法,但是因为种种原因,并没有使用最小接口(我认为这种设计是非常正常的),而是用了这个java.util.Map接口。
在使用这个api的时候,我们不幸的没有一个Map对象,而只有一个proprietary的PropertyMap(或者HttpServletRequest也行)对象:
需要写个adapter吧?目标代码如下:
那么这个convertToMap怎么写呢?
Map接口相当的肥厚阿。继承AbstractMap的话要实现entrySet(),我们也没有。而我们明明知道runApi()只需要一个能支持get()的Map就成了。
一个方法呢,就是用dimple,下面是一个pojo的adapter(不依赖dimple):
只需要实现我们关心的方法(一个安全的方法,可以显然PropertyMapAdapter implements Map,这样就可以用eclipse的代码生成来选择性地实现这些我们关心的方法,不会有typo。),不用管其它的乱七八糟的。
然后使用dimple,实现convertToMap如下:
搞定。
可见,dimple并不是仅仅可以用做aop的interceptor,它还是一个克服静态类型局限的OO利器亚。而且不象aspectj,相当的light-weight,没有什么侵入性。
不知道大家遇到过这样的情况没有。有一个api,有这个接口签名:
runApi(Map map){ ... map.get(...); ... }
它仅仅使用get()一个方法,但是因为种种原因,并没有使用最小接口(我认为这种设计是非常正常的),而是用了这个java.util.Map接口。
在使用这个api的时候,我们不幸的没有一个Map对象,而只有一个proprietary的PropertyMap(或者HttpServletRequest也行)对象:
interface PropertyMap { String getProperty(String key); boolean containsKey(String key); Set keySet(); }
需要写个adapter吧?目标代码如下:
PropertyMap pmap = ...; runApi(convertToMap(pmap));
那么这个convertToMap怎么写呢?
Map接口相当的肥厚阿。继承AbstractMap的话要实现entrySet(),我们也没有。而我们明明知道runApi()只需要一个能支持get()的Map就成了。
一个方法呢,就是用dimple,下面是一个pojo的adapter(不依赖dimple):
public class PropertyMapAdapter { private final PropertyMap pmap; public Object get(Object key) { if(key instanceof String) { return pmap.get((String)key); } else return null; } public boolean containsKey(Object key) { if(key instanceof String) { return pmap.containsKey((String)key); } else return false; } public Set keySet() { return pmap.keySet(); } public boolean isEmpty() { return keySet().isEmpty(); } public int size() { return keySet().size(); } }
只需要实现我们关心的方法(一个安全的方法,可以显然PropertyMapAdapter implements Map,这样就可以用eclipse的代码生成来选择性地实现这些我们关心的方法,不会有typo。),不用管其它的乱七八糟的。
然后使用dimple,实现convertToMap如下:
Map convertToMap(PropertyMap pmap) { return Implementor.proxy(Map.class, new PropertyMapAdapter(pmap)); }
搞定。
可见,dimple并不是仅仅可以用做aop的interceptor,它还是一个克服静态类型局限的OO利器亚。而且不象aspectj,相当的light-weight,没有什么侵入性。
它仅仅使用get()一个方法,但是因为种种原因,并没有使用最小接口(我认为这种设计是非常正常的),而是用了这个java.util.Map接口。
看了这个例子,我的看法:
如果在视线中 需要这么复杂的代码来做测试,那么就意味着个这个接口设计并不“正常”了,根据你提到的“仅仅用到了map.get()"的这个意思,那么接口应该这样设计:
map.get()--->Object o runApi(Map map)--->runApi(Object o) { // not dependent on map}
这个例子我存在异议。 看着有点困惑。
如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?
相关推荐
风光储直流微电网Simulink仿真模型:光伏发电、风力发电与混合储能系统的协同运作及并网逆变器VSR的研究,风光储直流微电网Simulink仿真模型:MPPT控制、混合储能系统、VSR并网逆变器的设计与实现,风光储、风光储并网直流微电网simulink仿真模型。 系统由光伏发电系统、风力发电系统、混合储能系统(可单独储能系统)、逆变器VSR?大电网构成。 光伏系统采用扰动观察法实现mppt控制,经过boost电路并入母线; 风机采用最佳叶尖速比实现mppt控制,风力发电系统中pmsg采用零d轴控制实现功率输出,通过三相电压型pwm变器整流并入母线; 混合储能由蓄电池和超级电容构成,通过双向DCDC变器并入母线,并采用低通滤波器实现功率分配,超级电容响应高频功率分量,蓄电池响应低频功率分量,有限抑制系统中功率波动,且符合储能的各自特性。 并网逆变器VSR采用PQ控制实现功率入网。 ,风光储; 直流微电网; simulink仿真模型; 光伏发电系统; 最佳叶尖速比控制; MPPT控制; Boost电路; 三相电压型PWM变换器;
以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手。
【Python毕设】根据你提供的课程代码,自动排出可行课表,适用于西工大选课_pgj
【毕业设计】[零食商贩]-基于vue全家桶+koa2+sequelize+mysql搭建的移动商城应用
电动汽车充电背景下的微电网谐波抑制策略与风力发电系统仿真研究,电动汽车充电微电网的谐波抑制策略与风力发电系统仿真研究,基于电动汽车充电的微电网谐波抑制策略研究,包括电动汽车充电负 载模型,风电模型,光伏发现系统,储能系统,以及谐波处理模块 风力发电系统仿真 ,电动汽车充电负载模型; 风电模型; 光伏发现系统; 储能系统; 谐波处理模块; 风力发电系统仿真,电动汽车充电微电网的谐波抑制策略研究:整合负载模型、风电模型与光伏储能系统
Vscode部署本地Deepseek的continue插件windows版本
内容概要:本文详细介绍了滤波器的两个关键参数——截止频率(F0)和品质因素(Q),并探讨了不同类型的滤波器(包括低通、高通、带通和带阻滤波器)的设计方法及其特性。文章首先明确了F0和Q的基本概念及其在滤波器性能中的作用,接着通过数学推导和图形展示的方式,解释了不同Q值对滤波器频率响应的影响。文中特别指出,通过调整Q值可以控制滤波器的峰谷效果和滚降速度,进而优化系统的滤波性能。此外,还讨论了不同类型滤波器的具体应用场景,如低通滤波器适用于消除高频噪声,高通滤波器用于去除直流分量和低频干扰,而带通滤波器和带阻滤波器分别用于选取特定频段信号和排除不需要的频段。最后,通过对具体案例的解析,帮助读者更好地理解和应用相关理论。 适合人群:电子工程及相关领域的技术人员、研究人员以及高校学生,特别是那些需要深入了解滤波器设计原理的人群。 使用场景及目标:适用于从事模拟电路设计的专业人士,尤其是希望掌握滤波器设计细节和技术的应用场合。目标是让读者能够灵活运用Q值和F0来优化滤波器设计,提升系统的信噪比和选择性,确保信号的纯净性和完整性。
内容概要:本文主要讲述了利用QUARTUSⅡ进行电子设计自动化的具体步骤和实例操作,详细介绍了如何利用EDA技术在QUARTUSⅡ环境中设计并模拟下降沿D触发器的工作过程,重点探讨了系统规格设计、功能描述、设计处理、器件编译和测试四个步骤及相关的设计验证流程,如功能仿真、逻辑综合及时序仿真等内容,并通过具体的操作指南展示了电路设计的实际操作方法。此外还强调了QUARTUSⅡ作为一款集成了多种功能的综合平台的优势及其对于提高工作效率的重要性。 适用人群:电子工程、自动化等相关专业的学生或者工程师,尤其适用于初次接触EDA技术和QuartusⅡ的用户。 使用场景及目标:旨在帮助用户理解和掌握使用QUARTUSⅡ这一先进的EDA工具软件进行从概念设计到最后成品制作整个电路设计过程的方法和技巧。目标是在实际工作中能够熟练运用QUARTUSⅡ完成各类复杂电子系统的高效设计。 其他说明:文中通过具体的案例让读者更直观理解EDA设计理念和技术特点的同时也为进一步探索EDA领域的前沿课题打下了良好基础。此外它还提到了未来可能的发展方向,比如EDA工具的功能增强趋势等。
Simulink建模下的光储系统与IEEE33节点配电网的协同并网运行:光照强度变化下的储能系统优化策略与输出性能分析,Simulink模型下的光伏微网系统:光储协同,实现380v电压等级下的恒定功率并网与平抑波动,Simulink含光伏的IEEE33节点配电网模型 微网,光储系统并网运行 光照强度发生改变时,储能可以有效配合光伏进行恒定功率并网,平抑波动,实现削峰填谷。 总的输出有功为270kw(图23) 无功为0 检验可以并网到电压等级为380v的电网上 逆变侧输出电压电流稳定(图4) ,Simulink; 含光伏; 配电网模型; 微网; 光储系统; 储能配合; 恒定功率并网; 电压等级; 逆变侧输出。,Simulink光伏微网模型:光储协同并网运行,实现功率稳定输出
基于Andres ELeon新法的双馈风机次同步振荡抑制策略:附加阻尼控制(SDC)的实践与应用,双馈风机次同步振荡的抑制策略研究:基于转子侧附加阻尼控制(SDC)的应用与效能分析,双馈风机次同步振荡抑制策略(一) 含 基于转子侧附加阻尼控制(SDC)的双馈风机次同步振荡抑制,不懂就问, 附加阻尼控制 (SDC)被添加到 RSC 内部控制器的q轴输出中。 这种方法是由Andres ELeon在2016年提出的。 该方法由增益、超前滞后补偿器和带通滤波器组成。 采用实测的有功功率作为输入信号。 有关更多信息,你可以阅读 Andres ELeon 的lunwen。 附lunwen ,关键词:双馈风机、次同步振荡、抑制策略;转子侧附加阻尼控制(SDC);RSC内部控制器;Andres ELeon;增益;超前滞后补偿器;带通滤波器;实测有功功率。,双馈风机次同步振荡抑制技术:基于SDC与RSCq轴控制的策略研究
springboot疫情防控期间某村外出务工人员信息管理系统--
高效光伏并网发电系统MATLAB Simulink仿真设计与MPPT技术应用及PI调节闭环控制,光伏并网发电系统MATLAB Simulink仿真设计:涵盖电池、BOOST电路、逆变电路及MPPT技术效率提升,光伏并网发电系统MATLAB Simulink仿真设计。 该仿真包括电池,BOOST升压电路,单相全桥逆变电路,电压电流双闭环控制部分;应用MPPT技术,提高光伏发电的利用效率。 采用PI调节方式进行闭环控制,SPWM调制,采用定步长扰动观测法,对最大功率点进行跟踪,可以很好的提高发电效率和实现并网要求。 ,光伏并网发电系统; MATLAB Simulink仿真设计; 电池; BOOST升压电路; 单相全桥逆变电路; 电压电流双闭环控制; MPPT技术; PI调节方式; SPWM调制; 定步长扰动观测法。,光伏并网发电系统Simulink仿真设计:高效MPPT与PI调节控制策略
PFC 6.0高效循环加载系统:支持半正弦、半余弦及多级变荷载功能,PFC 6.0循环加载代码:支持半正弦、半余弦及多级变荷载的强大功能,PFC6.0循环加载代码,支持半正弦,半余弦函数加载,中间变荷载等。 多级加载 ,PFC6.0; 循环加载代码; 半正弦/半余弦函数加载; 中间变荷载; 多级加载,PFC6.0多级半正弦半余弦循环加载系统
某站1K的校园跑腿小程序 多校园版二手市场校园圈子失物招领 食堂/快递代拿代买跑腿 多校版本,多模块,适合跑腿,外卖,表白,二手,快递等校园服务 需要自己准备好后台的服务器,已认证的小程序,备案的域名!
【Python毕设】根据你提供的课程代码,自动排出可行课表,适用于西工大选课
COMSOL锂枝晶模型:五合一的相场、浓度场与电场模拟研究,涵盖单枝晶定向生长、多枝晶生长及无序生长等多元现象的探索,COMSOL锂枝晶模型深度解析:五合一技术揭示单枝晶至雪花枝晶的生长机制与物理场影响,comsol锂枝晶模型 五合一 单枝晶定向生长、多枝晶定向生长、多枝晶随机生长、无序生长随机形核以及雪花枝晶,包含相场、浓度场和电场三种物理场(雪花枝晶除外),其中单枝晶定向生长另外包含对应的参考文献。 ,comsol锂枝晶模型; 五合一模型; 单枝晶定向生长; 多枝晶定向生长; 多枝晶随机生长; 无序生长随机形核; 雪花枝晶; 相场、浓度场、电场物理场; 参考文献,COMSOL锂枝晶模型:多场景定向生长与相场电场分析
嵌入式大学生 点阵代码
那个有delphi12 tedgebrowser 使用的dll
基于DQN算法的微网储能优化调度与能量管理:深度强化学习的应用与实践,基于DQN算法的微网储能优化调度与能量管理:深度强化学习的应用与实践,基于DQN算法的微网储能运行优化与能量管理 关键词:微网 优化调度 储能优化 深度强化学习 DQN 编程语言:python 参考文献:《Explainable AI Deep Reinforcement Learning Agents for Residential Demand Side Cost Savings in Smart Grids》 内容简介: 受深层强化学习(RL)最新进展的激励,我们开发了一个RL代理来管理家庭中存储设备的操作,旨在最大限度地节省需求侧的成本。 所提出的技术是数据驱动的,并且RL代理从头开始学习如何在可变费率结构下有效地使用能量存储设备,即收缩“黑匣子”的概念,其中代理所学的技术被忽略。 我们解释了RL-agent的学习过程,以及基于存储设备容量的策略。 ,微网; 优化调度; 储能优化; 深度强化学习; DQN; 家庭存储设备; 需求侧成本节省; 智能电网; RL代理; 能量存储设备。,基于DQN算法的微网储
内容概要:该文档为FM17580的原理图设计文件,重点介绍了这款非接触式IC卡读写芯片的电路设计细节。文档详细列出了各个元器件及其连接方式、引脚分配及具体值设定。特别值得注意的是,为了确保性能和可靠性,在PCB布局时强调了GND线需要尽量以最短路径连回FM175xx芯片的TVSS引脚附近,并且靠近电源输入端(TVDD)。同时明确了FM17580只兼容SPI通讯协议,其他如IIC或UART选项则不在支持范围内。此外还提供了关于降低能耗的选择——移除不必要的ADC检测电路,这对于一些特定应用场景非常有用。 适合人群:具备硬件开发经验和RFID/NFC领域基础知识的技术人员或研究人员。 使用场景及目标:适用于需要详细了解FM17580内部结构和技术特性的项目团队;旨在帮助工程师们快速上手搭建实验平台并测试FM17580的功能特性。主要目的是为实际应用开发提供技术支持和参考。 其他说明:文档最后附带了一些附加信息,包括设计师名字、公司名称以及审查流程的相关内容,但具体内容并未公开。此外还提到该文档是针对FM17580评估板(即FM17580Demo)的设计图纸。文中出现多次类似表格可能是不同版本之间的对比或者记录修改历史的部分内容。