【发布时间】:2026-02-10 14:00:01
【问题描述】:
问题正在从ServiceLocator 反模式转移到Dependency Injection。鉴于本人经验不足,无法将DI原理转移到现在实现的代码上。
总结
摘要部分是可选的阅读。您可能想对某事发表评论,建议。
该程序的主要目的是合并特定信息的占位符字段的过程。信息的数量使得需要有基础设施。比如表单、服务、数据库。我对这项任务有一些经验。我设法基于WinForms 创建了一个类似的程序。它甚至可以工作!但就模式、维护、可扩展性和性能而言,代码很糟糕。重要的是要了解编程是一种爱好。这不是主要的教育或工作。
在WinForms 上实现所描述的任务的体验非常糟糕。我开始研究模式和新平台。起点是UWP 和MVVM。需要注意的是,绑定机制很神奇。
途中的第一个问题独立解决了。它与通过ShellPage 中的NavigationView 与ShellViewModel 连接的UWP 中的导航有关。除了创建一个NavigationService。它基于来自Windows Template Studio 的模板。
由于有一个有效的WinForms 程序及其反模式方向,所以有时间和愿望正确地做所有事情。
现在我面临一个架构问题。称为ServiceLocator(或ViewModelLocator)。我在Microsoft 的示例中找到了它,包括Windows Template Studio 的模板。在这样做的过程中,我又落入了反模式陷阱。如上所述,我不想再这样了。
作为解决方案的第一件事是dependency injection。鉴于本人经验不足,无法将DI原理转移到现在实现的代码上。
当前实现
应用程序UWP 的起点是app.xaml.cs。重点是将控制权转移到ActivationService。它的任务是将Frame 添加到Window.Current.Content 并导航到默认页面-MainPage。 Microsoft documentation.
ViewModelLocator 是一个单例。第一次调用它的属性会调用构造函数。
private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
// Constructor
private ViewModelLocator(){...}
使用ViewModelLocator 和View (Page) 是这样的,ShellPage:
private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;
使用ViewModelLocator 和ViewModel 类似,ShellViewModel:
private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;
转向 DI
ShellViewModel 具有来自ViewModelLocator 的NavigationService,如上所示。在这一点上我怎么能去DI?事实上,程序很小。现在是摆脱反模式的好时机。
代码
ViewModelLocator
private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
private ViewModelLocator()
{
// Services
SimpleIoc.Default.Register<NavigationService>();
// ViewModels and NavigationService items
Register<ShellViewModel, ShellPage>();
Register<MainViewModel, MainPage>();
Register<SettingsViewModel, SettingsPage>();
}
private void Register<TViewModel, TView>()
where TViewModel : class
where TView : Page
{
SimpleIoc.Default.Register<TViewModel>();
NavigationService.Register<TViewModel, TView>();
}
public ShellViewModel ShellViewModel => SimpleIoc.Default.GetInstance<ShellViewModel>();
public MainViewModel MainViewModel => SimpleIoc.Default.GetInstance<MainViewModel>();
public SettingsViewModel SettingsViewModel => SimpleIoc.Default.GetInstance<SettingsViewModel>();
public NavigationService NavigationService => SimpleIoc.Default.GetInstance<NavigationService>();
ShellPage:页面
private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;
public ShellPage()
{
InitializeComponent();
// shellFrame and navigationView from XAML
ViewModel.Initialize(shellFrame, navigationView);
}
ShellViewModel : ViewModelBase
private bool _isBackEnabled;
private NavigationView _navigationView;
private NavigationViewItem _selected;
private ICommand _itemInvokedCommand;
public ICommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new RelayCommand<NavigationViewItemInvokedEventArgs>(OnItemInvoked));
private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;
public bool IsBackEnabled
{
get => _isBackEnabled;
set => Set(ref _isBackEnabled, value);
}
public NavigationViewItem Selected
{
get => _selected;
set => Set(ref _selected, value);
}
public void Initialize(Frame frame, NavigationView navigationView)
{
_navigationView = navigationView;
_navigationView.BackRequested += OnBackRequested;
NavigationService.Frame = frame;
NavigationService.Navigated += Frame_Navigated;
NavigationService.NavigationFailed += Frame_NavigationFailed;
}
private void OnItemInvoked(NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
NavigationService.Navigate(typeof(SettingsViewModel));
return;
}
var item = _navigationView.MenuItems.OfType<NavigationViewItem>().First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
var pageKey = GetPageKey(item);
NavigationService.Navigate(pageKey);
}
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
NavigationService.GoBack();
}
private void Frame_Navigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = _navigationView.SettingsItem as NavigationViewItem;
return;
}
Selected = _navigationView.MenuItems
.OfType<NavigationViewItem>()
.FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType));
}
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw e.Exception;
}
private bool IsMenuItemForPageType(NavigationViewItem item, Type sourcePageType)
{
var pageKey = GetPageKey(item);
var navigatedPageKey = NavigationService.GetNameOfRegisteredPage(sourcePageType);
return pageKey == navigatedPageKey;
}
private Type GetPageKey(NavigationViewItem item) => Type.GetType(item.Tag.ToString());
更新 1
ServiceLocator 和 ViewModelLocator 之间的相等性我错了吗?
调用
ServiceLocator(或ViewModelLocator)
本质上,当前任务是连接View和ViewModel。 NavigationService 超出了此任务的范围。所以不应该在ViewModelLocator吗?
【问题讨论】:
-
你想做DI还是IOC?如您的示例代码所示,我没有看到构造函数注入的路径。
-
@Maess 我想从“坏”的 ServiceLocator 转移到“好”的解决方案。我发现的第一个,也许是唯一一个是依赖注入。这就是它结束的地方。如果这些是不同的、不相容的东西,为什么要放在一起提到。
标签: c# mvvm dependency-injection uwp service-locator