使用抽象和依赖注入,如果需要在UI中配置特定于实现的细节,该怎么办?

ror*_*.ap 8 c# abstraction dependency-injection mvvm separation-of-concerns

我有一个应用程序从输入文件加载客户端/事项编号列表,并在UI中显示它们.这些数字是简单的零填充数字字符串,如"02240/00106".这是ClientMatter班级:

public class ClientMatter
{
    public string ClientNumber { get; set; }
    public string MatterNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我正在使用MVVM,它使用依赖注入和UI中包含的组合根.有一个IMatterListLoader服务接口,其中实现表示从不同文件类型加载列表的机制.为简单起见,假设只有一个实现与应用程序一起使用,即应用程序目前不支持多种文件类型.

public interface IMatterListLoader
{
    IReadOnlyCollection<string> MatterListFileExtensions { get; }
    IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile);
}
Run Code Online (Sandbox Code Playgroud)

让我们说在我的初始版本中,我选择了一个MS Excel实现来加载事项列表,如下所示:

在此输入图像描述

我想允许用户在运行时配置列表开始的行号和列号,因此视图可能如下所示:

在此输入图像描述

这是MS Excel的实现IMatterListLoader:

public sealed class ExcelMatterListLoader : IMatterListLoader
{
    public uint StartRowNum { get; set; }
    public uint StartColNum { get; set; }
    public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }

    public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
    {
        // load using StartRowNum and StartColNum
    }
}
Run Code Online (Sandbox Code Playgroud)

行号和列号是特定于MS Excel实现的实现细节,并且视图模型不知道它.然而,MVVM决定了我控制视图模型视图属性,所以如果我这样做,这将是这样的:

public sealed class MainViewModel
{
    public string InputFilePath { get; set; }

    // These two properties really don't belong
    // here because they're implementation details
    // specific to an MS Excel implementation of IMatterListLoader.
    public uint StartRowNum { get; set; }
    public uint StartColNum { get; set; }

    public ICommandExecutor LoadClientMatterListCommand { get; }

    public MainViewModel(IMatterListLoader matterListLoader)
    {
        // blah blah
    }
}
Run Code Online (Sandbox Code Playgroud)

仅作比较,这是一个基于ASCII文本文件的实现,我可能会考虑下一个版本的应用程序:

在此输入图像描述

public sealed class TextFileMatterListLoader : IMatterListLoader
{
    public bool HasHeaderLine { get; set; }
    public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }

    public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
    {
        // load tab-delimited client/matters from each line
        // optionally skipping the header line.
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我没有MS Excel实现所需的行号和列号,但是我有一个布尔标志,指示客户端/事项编号是从第一行开始(即没有标题行)还是从第二行开始(即带标题行).

我相信视图模型应该不知道实现之间的变化IMatterListLoader.如何让视图模型完成控制演示文稿问题的工作,但仍然保留某些未知的实现细节?


这是依赖关系图:

在此输入图像描述

bic*_*bic 5

对于要加载的每种类型的文件,您都需要一个单独的视图模型。

每个视图模型都为其特定的加载器进行设置。

然后可以将这些视图模型作为依赖项传递给主视图模型,主视图模型在需要时调用每个视图模型;

public interface ILoaderViewModel
{
    IReadOnlyCollection<ClientMatter> Load();
}

public class ExcelMatterListLoaderViewModel : ILoaderViewModel
{
    private readonly ExcelMatterListLoader loader;

    public string InputFilePath { get; set; }

    public uint StartRowNum { get; set; }

    public uint StartColNum { get; set; }

    public ExcelMatterListLoaderViewModel(ExcelMatterListLoader loader)
    {
        this.loader = loader;
    }

    IReadOnlyCollection<ClientMatter> Load()
    {
        // Stuff

        loader.Load(fromFile);
    }
}

public sealed class MainViewModel
{
    private ExcelMatterListLoaderViewModel matterListLoaderViewModel;

    public ObservableCollection<ClientMatter> ClientMatters
        = new ObservableCollection<ClientMatter>();

    public MainViewModel(ExcelMatterListLoaderViewModel matterListLoaderViewModel)
    {
        this.matterListLoaderViewModel = matterListLoaderViewModel;
    }

    public void LoadCommand()
    {
        var clientMatters = matterListLoaderViewModel.Load();

        foreach (var matter in clientMatters)
        {
            ClientMatters.Add(matter)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当您向应用程序添加更多类型时,您将创建新的视图模型并将它们添加为依赖项。

  • 当您在 XAML 中使用隐式“DataTemplate”模式自动加载类型的正确视图时,这真的很出色。 (2认同)