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

В этой главе мы кратко обсудим внедрение зависимостей. Мы уже рассмотрели привязку данных, которая отделяет представления и модели представления друг от друга, что позволяет им общаться, не зная явно, что происходит на другом конце связи.

Теперь нам нужно нечто подобное, чтобы отделить нашу ViewModel от клиентских сервисов.

На заре объектно-ориентированного программирования разработчики столкнулись с проблемой создания и извлечения экземпляров классов в приложениях. Были предложены различные решения этой проблемы.

За последние несколько лет внедрение зависимостей и инверсия управления (IoC) приобрели популярность среди разработчиков и стали преобладать над некоторыми старыми решениями, такими как шаблон Singleton.

Внедрение зависимостей / Контейнеры IoC

IoC и внедрение зависимостей - это два тесно связанных шаблона проектирования, а контейнер - это, по сути, фрагмент кода инфраструктуры, который выполняет оба этих шаблона за вас.

  • Шаблон IoC предназначен для делегирования ответственности за построение, а шаблон внедрения зависимостей заключается в предоставлении зависимостей для уже созданного объекта.

  • Их можно рассматривать как двухэтапный подход к строительству. Когда вы используете контейнер, он берет на себя несколько обязанностей, а именно:

    • По запросу он создает объект.
    • Контейнер будет определять, от чего зависит этот объект.
    • Построение этих зависимостей.
    • Внедрение их в строящийся объект.
    • Рекурсивно выполняющийся процесс.

Давайте посмотрим, как мы можем использовать внедрение зависимостей, чтобы нарушить разделение между ViewModels и клиентскими службами. Мы подключим форму AddEditCustomerViewModel для обработки сохранения, используя связанную с ней инъекцию зависимостей.

Сначала нам нужно создать новый интерфейс в нашем проекте в папке 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(); 
      } 
   } 
}

Самый простой способ выполнить обработку сохранения - добавить новый экземпляр 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 более слабо разделены.

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