Проблема движется от анти-паттерна ServiceLocator
к Dependency Injection
. Ввиду моей неопытности я не могу перенести принцип DI на код, реализованный сейчас.
Summary
Раздел Summary не является обязательным для чтения. Вы можете что-то прокомментировать, посоветовать.
Основная цель программы - процесс объединения полей-заполнителей с указанными c данными. Количество информации делает необходимым иметь инфраструктуру вокруг. Такие как формы, сервисы, базы данных. У меня есть некоторый опыт с этой задачей. Мне удалось создать аналогичную программу на основе WinForms
. И это даже работает! Но с точки зрения шаблонов, обслуживания, расширяемости и производительности код ужасен. Важно понимать, что программирование - это хобби. Это не основное образование или работа.
Опыт выполнения описанного задания на WinForms
ужасен. Я начал изучать шаблоны и новую платформу. Начальная точка - UWP
и MVVM
. Следует отметить, что механизм связывания удивителен.
Первая проблема на пути была решена самостоятельно. Он связан с навигацией в UWP
через NavigationView
, расположенный в ShellPage
, связанном с ShellViewModel
. Вместе с созданием 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 .
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
имеет NavigationService
с ViewModelLocator
, как показано выше. Как я могу go к 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 : Page
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
?