В этой главе мы кратко обсудим внедрение зависимостей. Мы уже рассмотрели привязку данных, отделяющую виды Views и ViewModel друг от друга, что позволяет им общаться, не зная явно, что происходит на другом конце взаимодействия.
Теперь нам нужно нечто подобное, чтобы отделить нашу ViewModel от клиентских сервисов.
В первые дни объектно-ориентированного программирования разработчики сталкивались с проблемой создания и извлечения экземпляров классов в приложениях. Различные решения были предложены для этой проблемы.
За последние несколько лет внедрение зависимостей и инверсия управления (IoC) приобрели популярность среди разработчиков и имеют приоритет над некоторыми более старыми решениями, такими как шаблон Singleton.
Инъекция зависимостей / IoC-контейнеры
IoC и внедрение зависимостей — это два тесно связанных между собой шаблона проектирования, а контейнер — это, по сути, кусок кода инфраструктуры, который выполняет оба эти шаблона за вас.
-
Шаблон IoC предназначен для делегирования ответственности за построение, а шаблон внедрения зависимостей — для предоставления зависимостей объекту, который уже был создан.
-
Оба они могут рассматриваться как двухэтапный подход к конструированию. Когда вы используете контейнер, контейнер принимает на себя следующие обязанности:
- Он конструирует объект, когда его просят.
- Контейнер определит, от чего зависит этот объект.
- Построение этих зависимостей.
- Инъекция их в строящийся объект.
- Рекурсивно делаю процесс.
Шаблон IoC предназначен для делегирования ответственности за построение, а шаблон внедрения зависимостей — для предоставления зависимостей объекту, который уже был создан.
Оба они могут рассматриваться как двухэтапный подход к конструированию. Когда вы используете контейнер, контейнер принимает на себя следующие обязанности:
Давайте посмотрим, как мы можем использовать внедрение зависимостей, чтобы разорвать связь между ViewModels и клиентскими сервисами. Мы свяжем форму AddEditCustomerViewModel обработки сохранения с помощью внедрения зависимостей, связанного с этим.
Для начала нам нужно создать новый интерфейс в нашем проекте в папке Services. Если в вашем проекте нет папки services, сначала создайте ее и добавьте следующий интерфейс в папку Services.
using MVVMHierarchiesDemo.Model; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVVMHierarchiesDemo.Services { public interface ICustomersRepository { Task<List<Customer>> GetCustomersAsync(); Task<Customer> GetCustomerAsync(Guid id); Task<Customer> AddCustomerAsync(Customer customer); Task<Customer> UpdateCustomerAsync(Customer customer); Task DeleteCustomerAsync(Guid customerId); } }
Ниже приводится реализация ICustomersRepository.
using MVVMHierarchiesDemo.Model; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVVMHierarchiesDemo.Services { public class CustomersRepository : ICustomersRepository { ZzaDbContext _context = new ZzaDbContext(); public Task<List<Customer>> GetCustomersAsync() { return _context.Customers.ToListAsync(); } public Task<Customer> GetCustomerAsync(Guid id) { return _context.Customers.FirstOrDefaultAsync(c => c.Id == id); } public async Task<Customer> AddCustomerAsync(Customer customer){ _context.Customers.Add(customer); await _context.SaveChangesAsync(); return customer; } public async Task<Customer> UpdateCustomerAsync(Customer customer) { if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) { _context.Customers.Attach(customer); } _context.Entry(customer).State = EntityState.Modified; await _context.SaveChangesAsync(); return customer; } public async Task DeleteCustomerAsync(Guid customerId) { var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId); if (customer != null) { _context.Customers.Remove(customer); } await _context.SaveChangesAsync(); } } }
Простой способ сделать обработку Save — это добавить новый экземпляр ICustomersRepository в AddEditCustomerViewModel и перегрузить конструкторы AddEditCustomerViewModel и CustomerListViewModel.
private ICustomersRepository _repo; public AddEditCustomerViewModel(ICustomersRepository repo) { _repo = repo; CancelCommand = new MyIcommand(OnCancel); SaveCommand = new MyIcommand(OnSave, CanSave); }
Обновите метод OnSave, как показано в следующем коде.
private async void OnSave() { UpdateCustomer(Customer, _editingCustomer); if (EditMode) await _repo.UpdateCustomerAsync(_editingCustomer); else await _repo.AddCustomerAsync(_editingCustomer); Done(); } private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { target.FirstName = source.FirstName; target.LastName = source.LastName; target.Phone = source.Phone; target.Email = source.Email; }
Ниже приведен полный AddEditCustomerViewModel.
using MVVMHierarchiesDemo.Model; using MVVMHierarchiesDemo.Services; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVVMHierarchiesDemo.ViewModel { class AddEditCustomerViewModel : BindableBase { private ICustomersRepository _repo; public AddEditCustomerViewModel(ICustomersRepository repo) { _repo = repo; CancelCommand = new MyIcommand(OnCancel); SaveCommand = new MyIcommand(OnSave, CanSave); } private bool _EditMode; public bool EditMode { get { return _EditMode; } set { SetProperty(ref _EditMode, value); } } private SimpleEditableCustomer _Customer; public SimpleEditableCustomer Customer { get { return _Customer; } set { SetProperty(ref _Customer, value); } } private Customer _editingCustomer = null; public void SetCustomer(Customer cust) { _editingCustomer = cust; if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; Customer = new SimpleEditableCustomer(); Customer.ErrorsChanged += RaiseCanExecuteChanged; CopyCustomer(cust, Customer); } private void RaiseCanExecuteChanged(object sender, EventArgs e) { SaveCommand.RaiseCanExecuteChanged(); } public MyIcommand CancelCommand { get; private set; } public MyIcommand SaveCommand { get; private set; } public event Action Done = delegate { }; private void OnCancel() { Done(); } private async void OnSave() { UpdateCustomer(Customer, _editingCustomer); if (EditMode) await _repo.UpdateCustomerAsync(_editingCustomer); else await _repo.AddCustomerAsync(_editingCustomer); Done(); } private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { target.FirstName = source.FirstName; target.LastName = source.LastName; target.Phone = source.Phone; target.Email = source.Email; } private bool CanSave() { return !Customer.HasErrors; } private void CopyCustomer(Customer source, SimpleEditableCustomer target) { target.Id = source.Id; if (EditMode) { target.FirstName = source.FirstName; target.LastName = source.LastName; target.Phone = source.Phone; target.Email = source.Email; } } } }
Когда приведенный выше код скомпилирован и выполнен, вы увидите тот же вывод, но теперь ViewModels более свободно отсоединены.
Когда вы нажмете кнопку «Добавить клиента», вы увидите следующий вид. Когда пользователь оставляет любое поле пустым, оно подсвечивается, а кнопка сохранения становится недоступной.