Flutter中如何解决多模型双向关联引发的实例化死循环问题
循环依赖实例化死循环问题
问题场景
开发旅游类应用时定义Place、PlaceInfo、County、Category四个数据模型:
County类包含List<Place?>类型的places属性,存储区县下关联的景点列表Place类包含County类型的county属性,存储景点归属的区县信息
实例化对象时触发无限递归:创建Place对象会调用County构造方法生成关联的区县实例,创建County实例时又会调用Place构造方法生成区县下的景点列表,循环调用无法退出,始终无法完成对象实例化。
原有模型代码
class Place { final String id; final String name; final String image; final List<County?> county; final List<PlaceInfo> placeInfos; final String firstInfo; final List<Category?> categories; Place({ required this.id, required this.name, required this.image, required this.county, required this.placeInfos, required this.firstInfo, required this.categories, }); factory Place.fromMap(Map<String, dynamic> place) => Place( id: place["id"], name: place["name"], image: place["image"], county: List.from(Repository().countiesJSON.map((countyJSON) { if (countyJSON.containsValue(place["countyId"])) { return County.fromMap(countyJSON); } }).toList()), placeInfos: List<PlaceInfo>.from( place["information"].map( (item) => PlaceInfo.fromMap(item), ), ), firstInfo: place["firstInfo"], categories: List.from(place["categoriesId"] .map( (categoyId) => Repository() .categoriesJSON .map((categoryJSON) { if (categoryJSON.containsValue(categoyId)) { return Category.fromMap(categoryJSON); } }) .iterator .current, ) .toList())); } class PlaceInfo { final String infoTitle; final String info; PlaceInfo({ required this.infoTitle, required this.info, }); factory PlaceInfo.fromMap(Map<String, dynamic> placeInfo) => PlaceInfo( infoTitle: placeInfo["infoTile"], info: placeInfo["info"], ); } class Category { String id; final String name; final String image; final List<Place?> places; Category({ required this.id, required this.name, required this.image, required this.places, }); factory Category.fromMap(Map<String, dynamic> category) => Category( id: category["id"], name: category["name"], image: category["image"], places: List.from(category["placesId"] .map( (placeId) => Repository() .placesJSON .map((placeJSON) { if (placeJSON.containsValue(placeId)) { return Place.fromMap(placeId); } }) .iterator .current, ) .toList()), ); } class County { final String id; final String name; final String image; final List<Place?> places; County({ required this.id, required this.name, required this.image, required this.places, }); factory County.fromMap(Map<String, dynamic> county) => County( id: county["id"], name: county["name"], image: county["image"], places: List.from(county["placesId"] .map((placeId) => Repository().placesJSON.map((placeJSON) { if (placeJSON.containsValue(placeId)) { return Place.fromMap(placeJSON); } }).toList()) .toList()), ); }
测试演示数据
var placesData = [ { "id": "d", "name": "Dolmabahçe Sarayı", "image": "assets/images/dolmabahçe.jpg", "countyId": "b", "firstInfo": "Dolmabahçe Sarayı firstInfo", "information": [ {"infoTile": "bilgi 1", "info": "bilgi 1 açıklama"}, {"infoTile": "bilgi 2", "info": "bilgi 2 açıklama"}, {"infoTile": "bilgi 3", "info": "bilgi 3 açıklama"}, ], "categoriesId": [ "m", "s", ], }, { "id": "a", "name": "Ayasofya", "image": "assets/images/ayasofya.webp", "countyId": "s", "firstInfo": "Ayasofya firstInfo", "information": [ {"infoTile": "bilgi 1", "info": "bilgi 1 açıklama"}, {"infoTile": "bilgi 2", "info": "bilgi 2 açıklama"}, {"infoTile": "bilgi 3", "info": "bilgi 3 açıklama"}, ], "categoriesId": [ "c", ], }, { "id": "y", "name": "Yerebatan Sarnıcı", "image": "assets/images/yerebatan.webp", "countyId": "s", "firstInfo": "Yerebatan Sarnıcı firstInfo", "information": [ {"infoTile": "bilgi 1", "info": "bilgi 1 açıklama"}, {"infoTile": "bilgi 2", "info": "bilgi 2 açıklama"}, {"infoTile": "bilgi 3", "info": "bilgi 3 açıklama"}, ], "categoriesId": [ "m", ], }, ]; var categoriesData = [ { "id": "m", "name": "Müze", "image": "assets/images/müze.webp", "placesId": [ "d", "y", ], }, { "id": "r", "name": "Restorant", "image": "assets/images/restorant.webp", "placesId": [], }, { "id": "e", "name": "Eğlence", "image": "assets/images/eğlence.webp", "placesId": [], }, { "id": "s", "name": "Saray", "image": "assets/images/saray.webp", "placesId": [ "d", ], }, { "id": "a", "name": "Alışveriş", "image": "assets/images/alışveriş.webp", "placesId": [], }, { "id": "c", "name": "Cami", "image": "assets/images/cami.webp", "placesId": [ "a", ], }, { "id": "d", "name": "Doğa", "image": "assets/images/doğa.webp", "placesId": [], }, ]; var countiesData = [ { "id": "b", "name": "Beşiktaş", "image": "assets/images/beşiktaş.jpg", "placesId": [ "d", ], }, { "id": "s", "name": "Sultanahmet", "image": "assets/images/Sultanahmet.jpg", "placesId": [ "a", "y", ] } ];
问题根因
- 双向关联模型的
fromMap解析逻辑没有做实例缓存,每次遇到关联字段就递归新建关联对象,碰到循环引用就会无限嵌套调用 - 字段定义不符合业务逻辑:
Place类的county字段定义为List<County?>,实际业务中单个景点仅归属一个区县 - 关联匹配逻辑存在错误:使用
iterator.current遍历取值的写法无法正确获取匹配id的实例,多层列表嵌套转换逻辑冗余
修复方案
- 调整模型字段定义:将
Place类的county字段改为County?类型,匹配单归属的业务逻辑;将所有需要后续补全关联的字段设为可修改,支持分阶段赋值 - 增加全局实例缓存:分别建立
Place、County、Category的id到实例的缓存Map,同一个id的对象仅初始化一次,避免重复构造触发递归 - 分两阶段完成数据解析:
- 第一阶段:遍历所有原始JSON数据,仅解析各模型的基础非关联字段(id、名称、图片、景点详情、基础文本信息等),解析完成后存入对应缓存
- 第二阶段:基于缓存的实例,批量补全所有关联关系:给景点实例设置归属区县、关联分类列表,给区县实例设置下辖景点列表,给分类实例设置关联景点列表
- 替换错误的id匹配逻辑:使用
firstWhere方法精准匹配对应id的缓存实例,移除冗余的列表嵌套转换
内容的提问来源于stack exchange,提问作者Tulgar
相关产品推荐
相关产品推荐

