flutter因为性能的需要把dart的反射库dart:mirror给移除掉了,所以json只能反序列化成Map对象,无法转换或解析成具体的引用类型;所以无论是代码生成还是手工纯写都必须存在从map取值再赋值给引用类型的属性的代码。譬如:
class Person {
final String name;
final int age;
final int weight;
Person._(this.name, this.age, this.weight);
static Person fromJson(dynamic m) {
return Person._(name: m['name'], age: m['age'], weight: m['weight']);
}
Map<String, dynamic> toJson() {
final m = <String, dynamic>{};
m['name'] = name;
m['age'] = age;
m['weight'] = weight;
}
}
现在问题来了:一个网络请求来的数据要转换成具体类型的对象,这些赋值代码放在哪里呢,或者说要怎么组织起来呢? 因为网络请求需要转换的对象类型可能是非常多的!如果把这些代码耦合在上层业务里,这些冗余代码会显得非常累赘。我们当然希望的是网络请求只处理网络的数据,类型转换对其是不可见的,同时业务上层能够直接拿到具体类型的对象。
一种方案是把转换方法(例子中的Persion.fromJson)作为网络请求的参数,等待数据回来时显式调用,返回的就是具体类型,那请求方法就必须声明成范型方法,类似:
T request<T>(String url, T convert(dynamic)) {
...
return convert(data);
}
似乎解决了问题,但其实还是把转换交给了上层业务,而且网络请求参数带转换方法显得很笨拙,哪个网络库是这么写的?
可以类比retrofit是怎么写的:retrofit里把转换统一抽象成了converter接口,并定义了factory接口来创建两类converter, 从请求类型转数据的requestConverter和从数据转类型的responseConverter, factory的一个具体实现比如gson就做具体转换操作;这种抽象能够适配其它的反序列化库比如jackson,protobuf什么的。而gson又是如何知道具体类型的?这是利用了java反射的语言特性.
因为要适配所有类型的对象, 所以我们的请求接口肯定得声明成范型, 剩下的就是把类型和转换操作对应起来, 形如T: T func(Object). dart语言的反射是用不上了, 但妙的是dart支持类型作为键值! 也就是说可以用一个Map把这种对应关系建立起来! 这就很H了, 自定义的网络库有个统一的接受类型转换的map, 里面存储了各个类型转换的方法(前面说过, 这个转换无论是代码生成方式还是手工添加是必须要存在的), 在数据接收后统一做数据解析操作. 于是有:
typedef NetConverter<T> = T Function(dynamic response);
class NetClient {
NetClient(this._converters);
final _converters = <Type, NetConverter<dynamic>>{};
T request<T>(String url) {
...
final NetConverter<T> converter = _converters[T];
return converter(data);
}
}
业务层就写成:
final converters = <Type, NetConverter<dynamic>>{};
converters[Person] = Person.fromJson;
这里默认是把网络请求回来的数据转成Map, 再用通过我们注入的转换方法转成具体类型对象, 更彻底的把二进制转成我们需要的对象当然也能做. 这样不用把那坨转换或者解析操作带进上层业务, 同时对网络库也是透明的.
其实这在flutter里是屡见不鲜的, BuildContext.inheritFromWidgetOfExactType为什么能通过类型找对象, 为什么只能找最近祖先的对象, 原理也不过如此. 然而麻烦很快来了, 通常我们都有一个统一的返回格式,形如:
class NetResponse<T> {
const NetResponse(this.code, this.msg, this.data);
final int code;
final String msg;
final T data;
}
我们需要Person类型,实际返回的可能是NetResponse<Person>类型, 但
dart语言根本不支持Map[NetResponse<Person>] =这种写法! 而且需要满足对NetResponse是统一的转换方法, 对所带的范型<Person>是另一种转换方法, 看着都有点蛋疼啊!
这里需要先说明一下dart的类型系统. 我们都知道java存在类型擦除, 意思是在运行时List<String>和List是一个类型, 但dart是没有类型擦除的, List<String>和List不是一个类型! 更具体的: 在编译时 声明List类型其实是声明List<dyanmic>, 它的元素可以是任意对象, 但List<String>声明的对象元素只能是String对象!
第一个问题相对好解决.
类型可以作为键值,自然也可以作为返回值那么把范型作为返回值, 再用这个返回值作键值就可以了, 所以需要一个特殊方法typeOf:
Type typeOf<T>() => T;
注意这个特殊方法的返回值类型不应该是T, (如果是T意味着什么?)
NetResponse<Person>作为键值就应该写成
converters[typeOf<NetResponse<Person>>]
接着是第二个问题.
NetResponse显然需要一个统一处理的方法, 这个方法显然也是需要范型的:
NetResponse<T> from<T>(dynamic response) {
int code = response['code'];
String msg = resposne['msg'];
dynamic data = msg['data'];
...
return NetResponse(code, msg, );
}
问题的关键是data不知道具体类型, 还是需要一个转换方法, 但我们一开始不就是为了解决这个问题的么? 这时候遇到的情况有点像套娃, 转换一个类型需要先转换一个子类型; 其实这个问题我们已经给出答案了, 我们解决这个问题的方法就是用一个类型作为键值的map存储这个转换方法呀, 只不过再嵌套了一层而已. 于是有:
final secondary = <Tyep, NetConverter<dynamic>>{};
secondary[Person] = Person.from;
NetResponse<T> from<T>(dynamic response) {
int code = response['code'];
String msg = resposne['msg'];
dynamic data = msg['data'];
final NetConverter<T> convert = secondary[T];
return NetResponse(code, msg, convert(data));
}
final primary = <Tyep, NetConverter<dynamic>>{};
primary[typeOf<NetResponse<Person>>()] = from<Person>;
但最后一行写法语法上是不支持的, 于是这里遇到了第三个问题: 范型方法作为值无法携带范型类型; 必须有一个方法的返回值是类型明确的, 才能作为值传递, 于是有:
NetResponse<Person> _convertPerson(dynamic m) => from<Person>(m);
primary[typeOf<NetResponse<Person>>()] = _convertPerson;
网络库需要持有的是primary实例, 类似retrofit中的adapterfactory, 如果有另外一个Cat类型:
NetResponse<Cat> _convertCat(dynamic m) => from<Cat>(m);
primary[typeOf<NetResponse<Cat>>()] = _convertCat;
哪怕有个特殊请求的返回格式没有遵循NetResponse有没有关系了, 只要在primary添加这个特殊类型即可, 但不需要通过from<T>, 比如直接返回一个Dog:
primary[Dog] = Dog.from;
这样满足了我们所有的要求: 统一处理固定格式; 能够兼容特殊格式; 避免上层业务冗余; 透明下层数据处理;
美中不足的可能是填充这个类型大map要写这一大坨方法声明, 只要类型一多看着绝对很吃力, 这时候需要把代码生成工具利用起来了, 这属于另外一个话题, 它只是一个达到我们目的的手段.
另外一个可能的问题是这个secondaryMap要如何组织, 实践了一下, 最好还是用个类封装起来:
class ResponseConverter {
ResponseConverter(this._converters)
final <Tyep, NetConverter<dynamic>> _secondary;
NetResponse<T> makeResponse<T>(dymamic m) {
...
final NetConverter<T> converter = _secondary[T]; // 这里一定要声明NetConverter<T>!
final data = converter(m['data');
...
}
}
先注册ResponseConverter中的_secondary, 接着再注册primary:
final secondary = <Tyep, NetConverter<dynamic>>{};
secondary[Person] = Person.from;
final maker = ResponseConverter(secondary);
final primary = <Tyep, NetConverter<dynamic>>{};
NetResponse<Person> _convertPerson(dynamic m) => maker.makeResponse<Person>(m);
primary[typeOf<NetResponse<Person>>()] = _convertPerson;
primary[Dog] = Dog.from;
...
final client = NetClient(primary);
更细节就看业务代码怎么组织了。