2008-05-07
动态properties转换
今天同事和我讨论他遇到的一个问题。具体要求是这样的,在运行时,我们会从模块G得到一个Map,这个Map里面都是一些字符串对,你可以理解成一个字典,有字符串的key和字符串的value。简短节说,就是
非常非常复杂深奥。
好,现在我们事先知道要从这个map里读取一些数据点,比如:id, name, sex等等。
对id,我们知道读出来的是int;对name,是string;对sex,应该对应一个叫Gender的enum类型。
这就涉及一个自动类型转换的问题。我们希望不用对每个数据点做手工类型转换。
另外一个需求,一些数据点是有缺省值的。比如name我们可以缺省为空字符串。
这样,如果map里面没有某个值,我们就看缺省值,如果有,就用这个缺省值,如果没有,就抛异常。
手工做的话,大概是这样:
比较痛苦。于是做了一个动态代理:
convert()函数是调用apache的ConvertUtilsBean做的,没什么说的。
那么,用法呢?
这里面,对annotation的用法比较特别。不过不这么做,java也不提供一个简单并且类型安全的指定缺省值的方法。当然,如果你凑巧不需要缺省值,那么也不用annotation,直接用interface就好。
Map<String, String>
非常非常复杂深奥。
好,现在我们事先知道要从这个map里读取一些数据点,比如:id, name, sex等等。
对id,我们知道读出来的是int;对name,是string;对sex,应该对应一个叫Gender的enum类型。
这就涉及一个自动类型转换的问题。我们希望不用对每个数据点做手工类型转换。
另外一个需求,一些数据点是有缺省值的。比如name我们可以缺省为空字符串。
这样,如果map里面没有某个值,我们就看缺省值,如果有,就用这个缺省值,如果没有,就抛异常。
手工做的话,大概是这样:
String idValue = map.get("id");
if (idValue == null) {
throw ...;
}
int id = Integer.parseInt(idValue);
String name = map.get("name");
if (name == null) {
name = "";
}
String sexValue = map.get("sex");
if (sexValue == null) {
throw ...;
}
Gender sex = Gender.valueOf(sexValue);
...
比较痛苦。于是做了一个动态代理:
public final class PropertyConverter<T> {
private final Class<T> targetType;
private PropertyConverter(Class<T> targetType) {...}
public static <T> PropertyConverter<T> to(Class<T> targetType) {
return new PropertyConverter<T>(targetType);
}
public T from(final Map<String, String> map) {
return Proxy.newProxyInstance(
new Class[]{targetType}, targetType.getClassLoader(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
String value = map.get(method.getName());
if (value == null) {
Object defaultValue = method.getDefaultValue();
if (defaultValue == null) {
throw ...;
}
return defaultValue;
}
return convert(value, method.getReturnType());
}
});
}
}
convert()函数是调用apache的ConvertUtilsBean做的,没什么说的。
那么,用法呢?
@interface Foo {
int id();
String name() default "";
Gender sex();
}
Map<String, String> map = ...;
Foo foo = PropertyConverter.to(Foo.class).from(map);
foo.id();
foo.name();
这里面,对annotation的用法比较特别。不过不这么做,java也不提供一个简单并且类型安全的指定缺省值的方法。当然,如果你凑巧不需要缺省值,那么也不用annotation,直接用interface就好。
评论
upyaya
2008-07-19
恩,不错,method.getReturnType 都用上了。动态代理的价值全被体现了
kimmking
2008-07-12
apache beanutils中
dwr中
jsf myfaces中
都有基本类型的转换实现。
自己反射下属性(beanutils也可以帮忙),加自动转换类型。
lz的东西就好了。
dwr中
jsf myfaces中
都有基本类型的转换实现。
自己反射下属性(beanutils也可以帮忙),加自动转换类型。
lz的东西就好了。
soleghost
2008-07-12
签名,泛型,代理
都是自己平时很少用到的,运行一下,学习一下
都是自己平时很少用到的,运行一下,学习一下
racnow
2008-06-16
呵呵,精益求精啊,不过有个地方参数顺序好像错了:
11行,我看api里是下面这样的啊
public final class PropertyConverter<T> {
private final Class<T> targetType;
private PropertyConverter(Class<T> targetType) {...}
public static <T> PropertyConverter<T> to(Class<T> targetType) {
return new PropertyConverter<T>(targetType);
}
public T from(final Map<String, String> map) {
return Proxy.newProxyInstance(
new Class[]{targetType}, targetType.getClassLoader(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
String value = map.get(method.getName());
if (value == null) {
Object defaultValue = method.getDefaultValue();
if (defaultValue == null) {
throw ...;
}
return defaultValue;
}
return convert(value, method.getReturnType());
}
});
}
}
11行,我看api里是下面这样的啊
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
yufu
2008-06-10
感觉可以参考spring里面的类型转换吧,它好像用的是java的PropertyEditorSupport,感觉可以参考参考啊
williamy
2008-06-10
我觉得完全可以在这里问题上使用state模式,或者命令模式,或者使用aop来实现类型转换,那样楼主肯定觉得更优雅
bloodrate
2008-06-10
@interface Foo {
int id();
String name() default "";
Gender sex();
}
Map<String, String> map = ...;
Foo foo = PropertyConverter.to(Foo.class).from(map);
foo.id();
foo.name();
没见过这种用法,学习
int id();
String name() default "";
Gender sex();
}
Map<String, String> map = ...;
Foo foo = PropertyConverter.to(Foo.class).from(map);
foo.id();
foo.name();
没见过这种用法,学习
williamy
2008-05-21
搞了一堆代码去代替原来很清晰的一堆代码,有意思吗?
williamy
2008-05-21
不明白为什么需要这么处理,标准的简单问题复杂化
ajoo
2008-05-13
引用
我的方案也差不多啦,IDE 里面 rename 一下,然后也只要改一处字符串就好。
这个,没懂啊。怎么能只改一处字符串的?假设有10个test case都依赖Foo。
Elminster
2008-05-13
ajoo 写道
对,也不算什么大优势啦。其实测试驱动已经很多时候让类型安全变得不那么重要了。不过我总觉得,既然还是在用老土的java,还是应该注重静态类型安全和重构能力的。毕竟除了这个,java也没啥别的可以拿出来和别人比了。比如你的id这个key变成“uid”了,只要Eclipse里rename一下,世界就清净了,该改的ide都帮你改好了。总比跟着测试的红条条到处救火强吧?
我的方案也差不多啦,IDE 里面 rename 一下,然后也只要改一处字符串就好。
ajoo 写道
合辙就是用继承的么?可是这样不干净啊。一旦你不小心忘了重载某个method,行为又悄悄地依赖那个缺省的map了。而且如果你通通都override了,那还要那个碍眼的Map在那干啥?
不知道我们javaer喜欢final class的么?不知道我们喜欢用EasyMock而不是自己写子类的么?
不知道我们javaer喜欢final class的么?不知道我们喜欢用EasyMock而不是自己写子类的么?
行行,随你。这个话题再说下去就无聊了,就此打住。
另,收短信收短信。
ajoo
2008-05-12
引用
明白了。不过这个算不上什么优势吧?无非就是 id 对应的类型变了,测试代码若是忘了对应改变,你的方案可以在编译时报错,用 Map 的要等到跑测试的时候才发现对吧?考虑单元测试扮演的角色,不认为这是很大的区别,至少我的单元测试是直接放在“编译后动作”里面的。
对,也不算什么大优势啦。其实测试驱动已经很多时候让类型安全变得不那么重要了。不过我总觉得,既然还是在用老土的java,还是应该注重静态类型安全和重构能力的。毕竟除了这个,java也没啥别的可以拿出来和别人比了。比如你的id这个key变成“uid”了,只要Eclipse里rename一下,世界就清净了,该改的ide都帮你改好了。总比跟着测试的红条条到处救火强吧?
引用
嗯嗯,而且要实现你这个能力也不是不可以(先声明,我不喜欢这个玩法):
public final class Converter // 用组合了,省得你们这些讲究繁文缛节的 Java 程序员呱噪 ……
{
private final Map<String, String> _map;
public Base(Map<String, String> map) { _map = map; }
public T GetProperty<T>(Map<String, String> map, String key, T defaultValue)
{
String value = _map.get(key);
if (value == null) {
if (defaultValue == null) {
throw ...;
}
return defaultValue;
}
return convert(value);
}
}
public abstract class Foo
{
private final Converter _conv;
public virtual int id() { return _conv.GetProperty<int>("id<script type="text/javascript" src="http://www.javaeye.com/javascripts/tinymce/themes/advanced/langs/zh.js"></script><script type="text/javascript" src="http://www.javaeye.com/javascripts/tinymce/plugins/javaeye/langs/zh.js"></script>", null); }
public virtual String name() { return _conv.GetProperty<String>("name", ""); }
public virtual Gender sex() { return _conv.GetProperty<Gender>("sex", null); }
private final class Dummy : Foo {}
public static Foo Instance(Map<String, String> map)
{
Foo foo = new Dummy();
foo._conv = new Converter(map);
return foo;
}
}
测试代码要玩,可以自己继承一个 Foo 然后提供新的 id / name / sex 。
合辙就是用继承的么?可是这样不干净啊。一旦你不小心忘了重载某个method,行为又悄悄地依赖那个缺省的map了。而且如果你通通都override了,那还要那个碍眼的Map在那干啥?
不知道我们javaer喜欢final class的么?不知道我们喜欢用EasyMock而不是自己写子类的么?
Elminster
2008-05-12
ajoo 写道
引用
明白了,type erasure 的泛型总是出乎我意料地弱 …… 那么,在泛型函数内取 T 对应的 class 对象行不行?这个只需要编译器给泛型函数加一个隐藏的额外参数,应该是可以做的吧?
有一些办法的。比如Neal Gafter的Type token(通过一个子类来绕出来这个T的class),或者传统地传一个String.class进去,或者新的Reified Generics(vaporware)。
这么麻烦 …… 还不如 .net 的泛型啊,至少那个用 typeof(T) 就可以取出 T 对应的 Type 对象了。
ajoo 写道
引用
这个 …… 一般的方案难道不是直接给 FooClient 传一个 Foo 进去么?就算沦落到直接用 Map ,也木有把 map.put(...) 这种东西直接放在 FooClient 里的道理呀?
我们谈的是测试代码呀兄弟。
当然也是传一个Foo进去。问题是这里的Foo不是接口,而是一个行为依赖于Map的内容的具体类。你要模拟一个id=0的情况,不得在Foo里面的那个Map上下功夫?
比如:
@Mock Foo foo;
public void testIdIsZero() {
expect(foo.id()).andReturn(0); //表示foo.id()会被调用,我们对这个调用返回0.
replay();
assertEquals("expect to get this value", new FooClient(foo).run());
}
要是Foo是个行为依赖于Map的类的话,你怎么鼓捣?
明白了。不过这个算不上什么优势吧?无非就是 id 对应的类型变了,测试代码若是忘了对应改变,你的方案可以在编译时报错,用 Map 的要等到跑测试的时候才发现对吧?考虑单元测试扮演的角色,不认为这是很大的区别,至少我的单元测试是直接放在“编译后动作”里面的。
嗯嗯,而且要实现你这个能力也不是不可以(先声明,我不喜欢这个玩法):
public final class Converter // 用组合了,省得你们这些讲究繁文缛节的 Java 程序员呱噪 ……
{
private final Map<String, String> _map;
public Base(Map<String, String> map) { _map = map; }
public T GetProperty<T>(Map<String, String> map, String key, T defaultValue)
{
String value = _map.get(key);
if (value == null) {
if (defaultValue == null) {
throw ...;
}
return defaultValue;
}
return convert(value);
}
}
public abstract class Foo
{
private final Converter _conv;
public virtual int id() { return _conv.GetProperty<int>("id", null); }
public virtual String name() { return _conv.GetProperty<String>("name", ""); }
public virtual Gender sex() { return _conv.GetProperty<Gender>("sex", null); }
private final class Dummy : Foo {}
public static Foo Instance(Map<String, String> map)
{
Foo foo = new Dummy();
foo._conv = new Converter(map);
return foo;
}
}
测试代码要玩,可以自己继承一个 Foo 然后提供新的 id / name / sex 。
ajoo 写道
引用
你小子庸俗化了呀!当年那只特立独行的猪捏?猪肉涨价被人宰了吃了? :D
我管这叫“成熟”。 :)
唉,世界上的猪,又少了一头 ……
ajoo
2008-05-11
维护有两种不同的问题:
一是代码非常赃,非常乱,copy-paste漫天飞,字符串到处用。这种东西往往原理比较简单,大致看一看就知道“为什么”这么乱了,无非就是原来那位很懒,只知道最naive的解决方法。但是真正要把问题清理好,维护成功,仍然是个噩梦。具体这个问题,你可以写得到处都是if-else,到处都是字符串,也最直观容易懂。也可以用elm的方法,做一层封装,易懂程度差一些,但是代码干净了,不过测试起来,mock起Foo, Bar来,还是要隔着那个Map的逻辑来隔靴搔痒,仍然到处飞字符串。
二是系统看上去简洁干净,井井有条,所有的开关,布线都魔术式地隐藏起来,要让普通人维护日常整洁粉容易,但是需要很有经验,水平较高的工程师来理解这一切魔法到底如何实现的。这种“高科技”,往往你一旦学会了解了它内部机理,一切就迎刃而解。就比如这个东西,是用了比较不寻常的技巧,但是你只要懂动态代理,看明白了这个技巧,一切不过都是顺理成章。
看维护者到底是一个体力劳动者,还是脑力劳动者了。
一是代码非常赃,非常乱,copy-paste漫天飞,字符串到处用。这种东西往往原理比较简单,大致看一看就知道“为什么”这么乱了,无非就是原来那位很懒,只知道最naive的解决方法。但是真正要把问题清理好,维护成功,仍然是个噩梦。具体这个问题,你可以写得到处都是if-else,到处都是字符串,也最直观容易懂。也可以用elm的方法,做一层封装,易懂程度差一些,但是代码干净了,不过测试起来,mock起Foo, Bar来,还是要隔着那个Map的逻辑来隔靴搔痒,仍然到处飞字符串。
二是系统看上去简洁干净,井井有条,所有的开关,布线都魔术式地隐藏起来,要让普通人维护日常整洁粉容易,但是需要很有经验,水平较高的工程师来理解这一切魔法到底如何实现的。这种“高科技”,往往你一旦学会了解了它内部机理,一切就迎刃而解。就比如这个东西,是用了比较不寻常的技巧,但是你只要懂动态代理,看明白了这个技巧,一切不过都是顺理成章。
看维护者到底是一个体力劳动者,还是脑力劳动者了。
hb123_net
2008-05-11
如果真是这么个需求,这么来搞,我看接手维护的人要疯掉了,过度了吧
pojo
2008-05-11
那么维护可能就是大问题了。形式与内容不再是皮与肉的关系,而是衣服和身体的关系。它们可以在不同的时空产生,改变和销毁。
ajoo
2008-05-10
pojo 写道
有一点不明白。你如何保证形式(interface Foo)与内容(Map)的类型一致?
当然是手工的了,在写这个interface或者@interface的时候。总有一个地方要把类型不安全的字符串映射到安全的java函数上去。但是,就一次,不论在生产代码还是测试代码中。
ajoo
2008-05-10
引用
ajoo 写道
C++可以在编译时处理这种情况是因为它用扩展法。在编译的时候它知道T是String。而Java不知道。在getProperty()内部你要调用convert(targetType, value)的时候,你不知道targetType是什么。
明白了,type erasure 的泛型总是出乎我意料地弱 …… 那么,在泛型函数内取 T 对应的 class 对象行不行?这个只需要编译器给泛型函数加一个隐藏的额外参数,应该是可以做的吧?
有一些办法的。比如Neal Gafter的Type token(通过一个子类来绕出来这个T的class),或者传统地传一个String.class进去,或者新的Reified Generics(vaporware)。
引用
ajoo 写道
用proxy,字符串仅仅会在PropertyConverterTest一个类里面存在,而我会建立一些完全虚假的FooForTest接口和一些虚构的property,这些东西测试好了之后再也不会变了。
而客户代码因为直接依赖业务接口Foo,永远不会需要基于字符串进行测试。比如:
你看测试里我是不是再也不关心Map里面放的是什么了?反正直接mock一个Foo来用就是了。
用GetProperty的话,那么FooTest, BarTest, BazTest等等等等都要依赖字符串的。
而客户代码因为直接依赖业务接口Foo,永远不会需要基于字符串进行测试。比如:
@interface Foo {
int id();
String name() default "foo";
Gender sex() default UNKNOWN;
}
public class FooClient {
private final Function
, Foo> converter;
public void run(Map map) {
Foo foo = converter.from(map);
if (foo.id() == 1) {
...
}
if (foo.sex() == MALE) {
...
}
}
}
你看测试里我是不是再也不关心Map里面放的是什么了?反正直接mock一个Foo来用就是了。
用GetProperty的话,那么FooTest, BarTest, BazTest等等等等都要依赖字符串的。
这个 …… 一般的方案难道不是直接给 FooClient 传一个 Foo 进去么?就算沦落到直接用 Map ,也木有把 map.put(...) 这种东西直接放在 FooClient 里的道理呀?
我们谈的是测试代码呀兄弟。
当然也是传一个Foo进去。问题是这里的Foo不是接口,而是一个行为依赖于Map的内容的具体类。你要模拟一个id=0的情况,不得在Foo里面的那个Map上下功夫?
比如:
@Mock Foo foo;
public void testIdIsZero() {
expect(foo.id()).andReturn(0); //表示foo.id()会被调用,我们对这个调用返回0.
replay();
assertEquals("expect to get this value", new FooClient(foo).run());
}
要是Foo是个行为依赖于Map的类的话,你怎么鼓捣?
引用
ajoo 写道
我已经逐渐学会用大家喜欢用的词来讨论问题了,即使它<script type="text/javascript" src="http://www.javaeye.com/javascripts/tinymce/themes/advanced/langs/zh.js"></script><script type="text/javascript" src="http://www.javaeye.com/javascripts/tinymce/plugins/javaeye/langs/zh.js"></script>们不够精确,或者说穿了就是狗屁。
你小子庸俗化了呀!当年那只特立独行的猪捏?猪肉涨价被人宰了吃了? :D
我管这叫“成熟”。
ajoo
2008-05-10
[quote][quote="ajoo"]C++可以在编译时处理这种情况是因为它用扩展法。在编译的时候它知道T是String。而Java不知道。在getProperty()内部你要调用convert(targetType, value)的时候,你不知道targetType是什么。[/quote]
明白了,type erasure 的泛型总是出乎我意料地弱 …… 那么,在泛型函数内取 T 对应的 class 对象行不行?这个只需要编译器给泛型函数加一个隐藏的额外参数,应该是可以做的吧?[/quote]
有一些办法的。比如Neal Gafter的Type token(就是通过一个子类来绕出来这个T的类型),或者传统地传一个String.class进去,或者新的Reified Generics(vaporware)。
[quote]
[quote="ajoo"]用proxy,字符串仅仅会在PropertyConverterTest一个类里面存在,而我会建立一些完全虚假的FooForTest接口和一些虚构的property,这些东西测试好了之后再也不会变了。
而客户代码因为直接依赖业务接口Foo,永远不会需要基于字符串进行测试。比如:
[code]
@interface Foo {
int id();
String name() default "foo";
Gender sex() default UNKNOWN;
}
public class FooClient {
private final Function
pojo
2008-05-10
有一点不明白。你如何保证形式(interface Foo)与内容(Map)的类型一致?
发表评论
提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则
- 浏览: 179859 次

- 详细资料
搜索本博客
最近加入圈子
最新评论
-
动态properties转换
恩,不错,method.getReturnType 都用上了。动态代理的价值全被 ...
-- by upyaya -
动态properties转换
apache beanutils中 dwr中 jsf myfaces中 都有基 ...
-- by kimmking -
动态properties转换
签名,泛型,代理 都是自己平时很少用到的,运行一下,学习一下
-- by soleghost -
动态properties转换
呵呵,精益求精啊,不过有个地方参数顺序好像错了: public final c ...
-- by racnow -
动态properties转换
感觉可以参考spring里面的类型转换吧,它好像用的是java的Property ...
-- by yufu






评论排行榜