You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无妥协无冗余重构大型遗留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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 17:50:36