xamarin 分解庞大的遗留ViewModels/Views并实现不变性

t1qtbnec  于 2023-06-27  发布在  其他
关注(0)|答案(2)|浏览(105)

在我们的应用程序中,有一些非常可怕的ViewModel和Fragments,现在我们需要重构。它们是巨大的,当然应该被分解。但是我并不清楚分解的正确方式(类和数据加载的顺序会导致绑定困难
p.s.约束框架是法律和习惯(

  • 之前:巨大而杂乱的类 *
  1. public class HugeLegacyViewModel : ViewModelBase
  2. {
  3. public TextViewModel FooText {get; set;}
  4. public ListViewModel FooList {get; set;} = new ListViewModel();
  5. public TextViewModel BarText {get; set;}
  6. public ListViewModel BarList {get; set;} = new ListViewModel();
  7. public HugeLegacyViewModel(IDependancy1 dependancy1)
  8. {
  9. ...
  10. }
  11. public void LoadData() // is called after object creation
  12. {
  13. var data = ...
  14. FooText = data.Foo.TextDto;
  15. FooList = data.Foo.ListDto.ToListViewModel();
  16. BarText = data.Bar.TextDto;
  17. BarList = data.Bar.ListDto.ToListViewModel();
  18. }
  19. }
  20. public class HugeLegacyFragment : FragmentBase<HugeLegacyViewModel>
  21. {
  22. public void OnViewCreate(View view)
  23. {
  24. var fooText = view.FindLayoutById(Resources.Layout.fooTextLayout);
  25. var barText = view.FindLayoutById(Resources.Layout.barTextLayout);
  26. var fooList = view.FindLayoutById(Resources.Layout.fooListLayout);
  27. var barList = view.FindLayoutById(Resources.Layout.barListLayout);
  28. ShittyLegacyBinder.CreateBindings(
  29. () => fooText.Text == ViewModel.FooText.Text,
  30. () => fooText.Font == ViewModel.FooText.Font,
  31. () => barText.Text == ViewModel.BarText.Text,
  32. () => barText.Font == ViewModel.BarText.Font,
  33. () => fooList.List == ViewModel.FooList.List,
  34. () => fooList.Title == ViewModel.FooList.Title,
  35. () => barList.List == ViewModel.BarList.List,
  36. () => barList.Title == ViewModel.BarList.Title
  37. );
  38. }
  39. }
  • AFTER:一组小类 *
  1. public class FooViewModel : ViewModelBase
  2. {
  3. public string Text {get;}
  4. public ReadOnlyCollection List {get;}
  5. ...
  6. }
  7. public class CoolNewViewModel : ViewModelBase
  8. {
  9. public FooViewModel FooVm {get; private set;} = new FooViewModel();
  10. public BarViewModel BarVm {get; private set;} = new BarViewModel();
  11. public CoolNewViewModel(IDependancy1 dependancy1)
  12. {
  13. ...
  14. }
  15. public void LoadData()
  16. {
  17. var data = ...
  18. FooVm = data.Foo.ToVm();
  19. BarVm = data.Bar.ToVm();
  20. }
  21. ...
  22. }
  23. public class FooView : View
  24. {
  25. private FooViewModel _vm;
  26. private TextView _textVew;
  27. private ListView _listView;
  28. public FooView(FooViewModel vm, TextView textView, ListView listView)
  29. {
  30. ...
  31. ShittyLegacyBinder.CreateBindings(
  32. () => _textVew == _vm.Text,
  33. () => _listView == _vm.List
  34. );
  35. }
  36. }
  37. public class CoolNewFragment : FragmentBase<HugeLegacyViewModel>
  38. {
  39. private FooView? _foo;
  40. private BarView? _bar;
  41. public void OnViewCreate(View view)
  42. // issue is here: OnViewCreate() is called eralier than LoadData() in vm,
  43. // so Views are now binded to instances that will be replaced in LoadData()
  44. //
  45. // bindings lead to stub empty classes
  46. {
  47. _foo = new FooView(
  48. vm: ViewModel.FooVm,
  49. textView: view.FindLayoutById(Resources.Layout.fooTextLayout),
  50. listView: view.FindLayoutById(Resources.Layout.fooListLayout)
  51. );
  52. _bar = new BarView(
  53. vm: ViewModel.BarVm,
  54. textView: view.FindLayoutById(Resources.Layout.barTextLayout),
  55. listView: view.FindLayoutById(Resources.Layout.barListLayout)
  56. );
  57. }
  58. ...
  59. }

我想为新类的属性和字段实现不变性,但这相当具有挑战性
问题描述见上述评论:虽然属性应该是不可变的,但实际上在虚拟机中它们不能。数据在创建示例后加载,因此FooVm和BarVm将被覆盖。由于Fragment的代码在vm之前执行,因此覆盖后绑定将丢失(
下面提供了一些不那么好的解决方案:

  • 解决方案1:忘记不变性 *
  1. public class FooViewModel1 : ViewModelBase
  2. {
  3. public MutableString Text {get;}
  4. public Collection List {get;}
  5. public void ReplaceData(Foo data)
  6. {
  7. Text.String = data.Text;
  8. List.ReplaceAllWith(data.List);
  9. }
  10. }
  11. public class MediocreNewViewModel1 : ViewModelBase
  12. {
  13. public FooViewModel FooVm {get; private set;} = new FooViewModel();
  14. public BarViewModel BarVm {get; private set;} = new BarViewModel();
  15. public MediocreNewViewModel1(IDependancy1 dependancy1)
  16. {
  17. ...
  18. }
  19. public void LoadData()
  20. {
  21. var data = ...
  22. FooVm.ReplaceData(data.Foo);
  23. BarVm.ReplaceData(data.Bar);
  24. }
  25. ...
  26. }
  • 解决方案2:主虚拟机中的别名 *
  1. public class FooViewModel2 : ViewModelBase
  2. {
  3. public string Text {get;}
  4. public ReadOnlyCollection List {get;}
  5. ...
  6. }
  7. public class MediocreNewViewModel2 : ViewModelBase
  8. {
  9. private FooViewModel _fooVm = new FooViewModel();
  10. private BarViewModel _barVm = new BarViewModel();
  11. public string FooText => _fooVm.Text;
  12. public string FooList => _fooVm.List;
  13. public string BarText => _barVm.Text;
  14. public string FooList => _barVm.List;
  15. public MediocreNewViewModel2(IDependancy1 dependancy1)
  16. {
  17. ...
  18. }
  19. public void LoadData()
  20. {
  21. var data = ...
  22. _fooVm = data.Foo.ToVm();
  23. _barVm = data.Bar.ToVm();
  24. }
  25. ...
  26. }
  27. public class FooView2 : View
  28. {
  29. private TextView _textVew;
  30. private ListView _listView;
  31. public FooView2(string text, ReadOnlyCollection list TextView textView, ListView listView)
  32. {
  33. ...
  34. ShittyLegacyBinder.CreateBindings(
  35. () => _textVew == text,
  36. () => _listView == list
  37. );
  38. }
  39. }
  40. public class MediocreNewFragment2 : FragmentBase<HugeLegacyViewModel>
  41. {
  42. private FooView2? _foo;
  43. private BarView2? _bar;
  44. public void OnViewCreate(View view)
  45. {
  46. _foo = new FooView2(
  47. text: ViewModel.FooText,
  48. list: ViewModel.FooList,
  49. textView: view.FindLayoutById(Resources.Layout.fooTextLayout),
  50. listView: view.FindLayoutById(Resources.Layout.fooListLayout)
  51. );
  52. _bar = new BarView2(
  53. text: ViewModel.BarText,
  54. list: ViewModel.BarList,
  55. textView: view.FindLayoutById(Resources.Layout.barTextLayout),
  56. listView: view.FindLayoutById(Resources.Layout.barListLayout)
  57. );
  58. }
  59. ...
  60. }
  • 解决方案3:将视图Map到实际Vm* 的 Package 器类
  1. public class FooViewModel3 : ViewModelBase
  2. {
  3. public string Text {get;}
  4. public ReadOnlyCollection List {get;}
  5. ...
  6. }
  7. public class FooViewModelWrapper
  8. {
  9. private FooViewModel3 _vm;
  10. public string Text => _vm.Text;
  11. public ReadOnlyCollection List => _vm.List;
  12. public FooViewModelWrapper(FooViewModel3 vm)
  13. {
  14. ...
  15. }
  16. }
  17. public class MediocreNewViewModel3 : ViewModelBase
  18. {
  19. private FooViewModel _fooVm = new FooViewModel();
  20. private BarViewModel _barVm = new BarViewModel();
  21. public FooViewModelWrapper FooVm {get; private set;} = new FooViewModelWrapper(_fooVm);
  22. public BarViewModelWrapper BarVm {get; private set;} = new BarViewModelWrapper(_barVm);
  23. public CoolNewViewModel(IDependancy1 dependancy1)
  24. {
  25. ...
  26. }
  27. public void LoadData()
  28. {
  29. var data = ...
  30. _fooVm = data.Foo.ToVm();
  31. _barVm = data.Bar.ToVm();
  32. }
  33. ...
  34. }
  35. public class FooView3 : View
  36. {
  37. private FooViewModelWrapper _vm;
  38. private TextView _textVew;
  39. private ListView _listView;
  40. public FooView3(FooViewModelWrapper vm, TextView textView, ListView listView)
  41. {
  42. ...
  43. ShittyLegacyBinder.CreateBindings(
  44. () => _textVew == _vm.Text,
  45. () => _listView == _vm.List
  46. );
  47. }
  48. }
  49. public class MediocreNewFragment3 : FragmentBase<HugeLegacyViewModel>
  50. {
  51. private FooView3? _foo;
  52. private BarView3? _bar;
  53. public void OnViewCreate(View view)
  54. {
  55. _foo = new FooView3(
  56. vm: ViewModel.FooVm,
  57. textView: view.FindLayoutById(Resources.Layout.fooTextLayout),
  58. listView: view.FindLayoutById(Resources.Layout.fooListLayout)
  59. );
  60. _bar = new BarView3(
  61. vm: ViewModel.BarVm,
  62. textView: view.FindLayoutById(Resources.Layout.barTextLayout),
  63. listView: view.FindLayoutById(Resources.Layout.barListLayout)
  64. );
  65. }
  66. ...
  67. }

所以,问题:

有没有可能在没有妥协和样板代码的情况下重构它?

brvekthn

brvekthn1#

ViewModels和不变性

我想为新类的属性和字段实现不变性,但这相当具有挑战性
在.Net中,ViewModel通常是实现INotifyPropertyChanged接口的类。同样,ViewModel属性也会发生变化。(或者至少,变化不应该是一件令人惊讶的事情,也不应该是一件坏事。)它们可以是不可变的(当没有变化时),但这不是强制性的。
另一方面,想要实现不变性对于表示数据的**类来说完全有意义。
请记住,数据和视图模型是两个不同的概念。它们服务于不同的目的。

ViewModels类的目标:
  • 通过属性显示数据和命令。
  • 通知这些属性的任何更改。

所有这些都是为了支持绑定系统。

Data类目标:
  • 表示数据。

关于重构

据我所知你想要两样东西
1.通过引入更小和更专业的班级来减少一些大班的责任。(完美)
1.使更多的类不可变。(小心)
您所面临的技术挑战是在创建UI和关联的ViewModel之后加载实际数据。

解决此问题的方法是使用ViewModels,因为它们支持属性更改。

你可以***让更多的类成为不可变的,但是你至少需要1个 * 可变的 * ViewModel(来处理延迟到达的数据)。不可变的类是表示ViewModel公开的数据的类。

展开查看全部
ua4mk5z4

ua4mk5z42#

后来我多次面对这个问题,并得出了这样的解决方案:
每个ViewModel都有一个对应的View和一个方法Bind(TViewModel vm)。一个视图<=>一个视图模型。
View只处理VM的绑定内容,其他什么都不做。如果parent有一个可以为空的内容,View会等待内容接收值,然后绑定它。如果View有其他子View,它只调用它们的Bind()方法,并让它们处理绑定。
这有助于加载数据:每个ViewModel都是有效的和“可绑定的”。如果它的一些内容没有加载,它只是空的,一旦加载-准备在视图中使用。
示例:

  1. public class MainViewModel : ViewModel
  2. {
  3. public record ContentViewModels(ChildViewModel1 Child1, ChildViewModel2 Child2);
  4. public ContentViewModels? Content { get; private set; }
  5. public async Task LoadContent()
  6. {
  7. Content = await _loader.LoadContent();
  8. }
  9. }
  10. public class MainView : BindableView<MainViewModel>
  11. {
  12. private ChildView1 _childView1 = new();
  13. private ChildView2 _childView2 = new();
  14. public void Bind(MainViewModel vm)
  15. {
  16. new EBinding
  17. {
  18. //executes on every Content change
  19. () => BindVmContent(vm.Content),
  20. };
  21. }
  22. private void BindVmContent(MainViewModel.ContentViewModels? content)
  23. {
  24. if (content is null)
  25. return;
  26. _childView1.Bind(content.Child1);
  27. _childView2.Bind(content.Child2);
  28. }
  29. }
展开查看全部

相关问题