如何无妥协无冗余重构大型遗留ViewModel/View并实现不可变性?
大型ViewModel与Fragment重构:不可变性与绑定顺序的解决方案探讨
我们的应用中有一批设计糟糕的大型ViewModel和Fragment,亟需重构拆分。但拆分过程中遇到了绑定难题——类的初始化顺序与数据加载流程冲突,导致绑定逻辑失效。
注:项目使用自定义遗留绑定框架。
重构前:臃肿杂乱的类
public class HugeLegacyViewModel : ViewModelBase { public TextViewModel FooText {get; set;} public ListViewModel FooList {get; set;} = new ListViewModel(); public TextViewModel BarText {get; set;} public ListViewModel BarList {get; set;} = new ListViewModel(); public HugeLegacyViewModel(IDependancy1 dependancy1) { ... } public void LoadData() // 对象创建后才调用 { var data = ... FooText = data.Foo.TextDto; FooList = data.Foo.ListDto.ToListViewModel(); BarText = data.Bar.TextDto; BarList = data.Bar.ListDto.ToListViewModel(); } } public class HugeLegacyFragment : FragmentBase<HugeLegacyViewModel> { public void OnViewCreate(View view) { var fooText = view.FindLayoutById(Resources.Layout.fooTextLayout); var barText = view.FindLayoutById(Resources.Layout.barTextLayout); var fooList = view.FindLayoutById(Resources.Layout.fooListLayout); var barList = view.FindLayoutById(Resources.Layout.barListLayout); ShittyLegacyBinder.CreateBindings( () => fooText.Text == ViewModel.FooText.Text, () => fooText.Font == ViewModel.FooText.Font, () => barText.Text == ViewModel.BarText.Text, () => barText.Font == ViewModel.BarText.Font, () => fooList.List == ViewModel.FooList.List, () => fooList.Title == ViewModel.FooList.Title, () => barList.List == ViewModel.BarList.List, () => barList.Title == ViewModel.BarList.Title ); } }
重构后:拆分后的小型类(存在绑定问题)
public class FooViewModel : ViewModelBase { public string Text {get;} public ReadOnlyCollection List {get;} ... } public class CoolNewViewModel : ViewModelBase { public FooViewModel FooVm {get; private set;} = new FooViewModel(); public BarViewModel BarVm {get; private set;} = new BarViewModel(); public CoolNewViewModel(IDependancy1 dependancy1) { ... } public void LoadData() { var data = ... FooVm = data.Foo.ToVm(); BarVm = data.Bar.ToVm(); } ... } public class FooView : View { private FooViewModel _vm; private TextView _textVew; private ListView _listView; public FooView(FooViewModel vm, TextView textView, ListView listView) { ... ShittyLegacyBinder.CreateBindings( () => _textVew.Text == _vm.Text, () => _listView.List == _vm.List ); } } public class CoolNewFragment : FragmentBase<HugeLegacyViewModel> { private FooView? _foo; private BarView? _bar; public void OnViewCreate(View view) // 问题核心:OnViewCreate()比ViewModel的LoadData()执行早, // 视图绑定的是会被LoadData()替换的空实例,最终绑定失效 { _foo = new FooView( vm: ViewModel.FooVm, textView: view.FindLayoutById(Resources.Layout.fooTextLayout), listView: view.FindLayoutById(Resources.Layout.fooListLayout) ); _bar = new BarView( vm: ViewModel.BarVm, textView: view.FindLayoutById(Resources.Layout.barTextLayout), listView: view.FindLayoutById(Resources.Layout.barListLayout) ); } ... }
我希望为新类的属性和字段实现不可变性,但目前的流程导致这一目标难以实现:数据在ViewModel实例创建后才加载,因此FooVm和BarVm不得不被覆盖;而Fragment的视图初始化逻辑先于数据加载,绑定的实例会被替换,最终绑定失效。
几种不尽理想的解决方案
方案1:放弃不可变性
通过让子ViewModel的内部数据可变,避免替换整个实例,但违背了不可变性设计原则。
public class FooViewModel1 : ViewModelBase { public MutableString Text {get;} public Collection List {get;} public void ReplaceData(Foo data) { Text.String = data.Text; List.ReplaceAllWith(data.List); } } public class MediocreNewViewModel1 : ViewModelBase { public FooViewModel FooVm {get; private set;} = new FooViewModel(); public BarViewModel BarVm {get; private set;} = new BarViewModel(); public MediocreNewViewModel1(IDependancy1 dependancy1) { ... } public void LoadData() { var data = ... FooVm.ReplaceData(data.Foo); BarVm.ReplaceData(data.Bar); } ... }
方案2:主ViewModel中使用属性别名
在主ViewModel中暴露子ViewModel的具体属性,避免直接绑定子ViewModel实例,但会产生大量重复的属性包装代码,维护成本高。
public class FooViewModel2 : ViewModelBase { public string Text {get;} public ReadOnlyCollection List {get;} ... } public class MediocreNewViewModel2 : ViewModelBase { private FooViewModel _fooVm = new FooViewModel(); private BarViewModel _barVm = new BarViewModel(); public string FooText => _fooVm.Text; public ReadOnlyCollection FooList => _fooVm.List; public string BarText => _barVm.Text; public ReadOnlyCollection BarList => _barVm.List; public MediocreNewViewModel2(IDependancy1 dependancy1) { ... } public void LoadData() { var data = ... _fooVm = data.Foo.ToVm(); _barVm = data.Bar.ToVm(); } ... } public class FooView2 : View { private TextView _textVew; private ListView _listView; public FooView2(string text, ReadOnlyCollection list, TextView textView, ListView listView) { ... ShittyLegacyBinder.CreateBindings( () => _textVew.Text == text, () => _listView.List == list ); } } public class MediocreNewFragment2 : FragmentBase<HugeLegacyViewModel> { private FooView2? _foo; private BarView2? _bar; public void OnViewCreate(View view) { _foo = new FooView2( text: ViewModel.FooText, list: ViewModel.FooList, textView: view.FindLayoutById(Resources.Layout.fooTextLayout), listView: view.FindLayoutById(Resources.Layout.fooListLayout) ); _bar = new BarView2( text: ViewModel.BarText, list: ViewModel.BarList, textView: view.FindLayoutById(Resources.Layout.barTextLayout), listView: view.FindLayoutById(Resources.Layout.barListLayout) ); } ... }
方案3:使用包装类映射视图与实际ViewModel
通过包装类作为中间层,避免直接绑定会被替换的子ViewModel实例,但需要额外编写包装类,增加了代码冗余。
public class FooViewModel3 : ViewModelBase { public string Text {get;} public ReadOnlyCollection List {get;} ... } public class FooViewModelWrapper { private FooViewModel3 _vm; public string Text => _vm.Text; public ReadOnlyCollection List => _vm.List; public FooViewModelWrapper(FooViewModel3 vm) { _vm = vm; } // 需要实现通知属性变化的逻辑,否则绑定不会更新 public void UpdateVm(FooViewModel3 newVm) { _vm = newVm; // 触发属性变化通知(假设ViewModelBase有相关方法) NotifyPropertyChanged(nameof(Text)); NotifyPropertyChanged(nameof(List)); } } public class MediocreNewViewModel3 : ViewModelBase { private FooViewModel3 _fooVm = new FooViewModel3(); private BarViewModel3 _barVm = new BarViewModel3(); public FooViewModelWrapper FooVm {get; private set;} public BarViewModelWrapper BarVm {get; private set;} public MediocreNewViewModel3(IDependancy1 dependancy1) { FooVm = new FooViewModelWrapper(_fooVm); BarVm = new BarViewModelWrapper(_barVm); } public void LoadData() { var data = ... _fooVm = data.Foo.ToVm(); _barVm = data.Bar.ToVm(); FooVm.UpdateVm(_fooVm); BarVm.UpdateVm(_barVm); } ... } public class FooView3 : View { private FooViewModelWrapper _vm; private TextView _textVew; private ListView _listView; public FooView3(FooViewModelWrapper vm, TextView textView, ListView listView) { _vm = vm; _textVew = textView; _listView = listView; ShittyLegacyBinder.CreateBindings( () => _textVew.Text == _vm.Text, () => _listView.List == _vm.List ); } } public class MediocreNewFragment3 : FragmentBase<MediocreNewViewModel3> { private FooView3? _foo; private BarView3? _bar; public void OnViewCreate(View view) { _foo = new FooView3( vm: ViewModel.FooVm, textView: view.FindLayoutById(Resources.Layout.fooTextLayout), listView: view.FindLayoutById(Resources.Layout.fooListLayout) ); _bar = new BarView3( vm: ViewModel.BarVm, textView: view.FindLayoutById(Resources.Layout.barTextLayout), listView: view.FindLayoutById(Resources.Layout.barListLayout) ); } ... }
提问
是否存在无需妥协不可变性、无冗余代码的重构方式?
内容的提问来源于stack exchange,提问作者stone-w4tch3r
相关产品推荐
相关产品推荐

