Учебники

MVVM — Внедрение зависимостей

В этой главе мы кратко обсудим внедрение зависимостей. Мы уже рассмотрели привязку данных, отделяющую виды 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 более свободно отсоединены.

Инъекция зависимостей MVVM MainWindow1

Когда вы нажмете кнопку «Добавить клиента», вы увидите следующий вид. Когда пользователь оставляет любое поле пустым, оно подсвечивается, а кнопка сохранения становится недоступной.