在我们的应用程序中,有一些非常可怕的ViewModel和Fragments,现在我们需要重构。它们是巨大的,当然应该被分解。但是我并不清楚分解的正确方式(类和数据加载的顺序会导致绑定困难
p.s.约束框架是法律和习惯(
- 之前:巨大而杂乱的类 *
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() // is called after object creation
{
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
);
}
}
- AFTER:一组小类 *
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 == _vm.Text,
() => _listView == _vm.List
);
}
}
public class CoolNewFragment : FragmentBase<HugeLegacyViewModel>
{
private FooView? _foo;
private BarView? _bar;
public void OnViewCreate(View view)
// issue is here: OnViewCreate() is called eralier than LoadData() in vm,
// so Views are now binded to instances that will be replaced in LoadData()
//
// bindings lead to stub empty classes
{
_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)
);
}
...
}
我想为新类的属性和字段实现不变性,但这相当具有挑战性
问题描述见上述评论:虽然属性应该是不可变的,但实际上在虚拟机中它们不能。数据在创建示例后加载,因此FooVm和BarVm将被覆盖。由于Fragment的代码在vm之前执行,因此覆盖后绑定将丢失(
下面提供了一些不那么好的解决方案:
- 解决方案1:忘记不变性 *
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:主虚拟机中的别名 *
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 string FooList => _fooVm.List;
public string BarText => _barVm.Text;
public string FooList => _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,
() => _listView == 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:将视图Map到实际Vm* 的 Package 器类
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)
{
...
}
}
public class MediocreNewViewModel3 : ViewModelBase
{
private FooViewModel _fooVm = new FooViewModel();
private BarViewModel _barVm = new BarViewModel();
public FooViewModelWrapper FooVm {get; private set;} = new FooViewModelWrapper(_fooVm);
public BarViewModelWrapper BarVm {get; private set;} = new BarViewModelWrapper(_barVm);
public CoolNewViewModel(IDependancy1 dependancy1)
{
...
}
public void LoadData()
{
var data = ...
_fooVm = data.Foo.ToVm();
_barVm = data.Bar.ToVm();
}
...
}
public class FooView3 : View
{
private FooViewModelWrapper _vm;
private TextView _textVew;
private ListView _listView;
public FooView3(FooViewModelWrapper vm, TextView textView, ListView listView)
{
...
ShittyLegacyBinder.CreateBindings(
() => _textVew == _vm.Text,
() => _listView == _vm.List
);
}
}
public class MediocreNewFragment3 : FragmentBase<HugeLegacyViewModel>
{
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)
);
}
...
}
所以,问题:
有没有可能在没有妥协和样板代码的情况下重构它?
2条答案
按热度按时间brvekthn1#
ViewModels和不变性
我想为新类的属性和字段实现不变性,但这相当具有挑战性
在.Net中,ViewModel通常是实现
INotifyPropertyChanged
接口的类。同样,ViewModel属性也会发生变化。(或者至少,变化不应该是一件令人惊讶的事情,也不应该是一件坏事。)它们可以是不可变的(当没有变化时),但这不是强制性的。另一方面,想要实现不变性对于表示数据的**类来说完全有意义。
请记住,数据和视图模型是两个不同的概念。它们服务于不同的目的。
ViewModels类的目标:
所有这些都是为了支持绑定系统。
Data类目标:
关于重构
据我所知你想要两样东西
1.通过引入更小和更专业的班级来减少一些大班的责任。(完美)
1.使更多的类不可变。(小心)
您所面临的技术挑战是在创建UI和关联的ViewModel之后加载实际数据。
解决此问题的方法是使用ViewModels,因为它们支持属性更改。
你可以***让更多的类成为不可变的,但是你至少需要1个 * 可变的 * ViewModel(来处理延迟到达的数据)。不可变的类是表示ViewModel公开的数据的类。
ua4mk5z42#
后来我多次面对这个问题,并得出了这样的解决方案:
每个ViewModel都有一个对应的View和一个方法Bind(TViewModel vm)。一个视图<=>一个视图模型。
View只处理VM的绑定内容,其他什么都不做。如果parent有一个可以为空的内容,View会等待内容接收值,然后绑定它。如果View有其他子View,它只调用它们的Bind()方法,并让它们处理绑定。
这有助于加载数据:每个ViewModel都是有效的和“可绑定的”。如果它的一些内容没有加载,它只是空的,一旦加载-准备在视图中使用。
示例: