MVVM –依存性注入
この章では、依存性注入について簡単に説明します。ビューとViewModelを相互に分離し、通信のもう一方の端で何が起こっているかを明示的に知らなくても通信できるようにするデータバインディングについては、すでに説明しました。
ここで、ViewModelをクライアントサービスから切り離すのと同様のものが必要です。
オブジェクト指向プログラミングの初期の頃、開発者はアプリケーションでクラスのインスタンスを作成および取得するという問題に直面していました。この問題に対して様々な解決策が提案されてきた。
過去数年間、依存性注入と制御の反転(IoC)は開発者の間で人気を博し、シングルトンパターンなどのいくつかの古いソリューションよりも優先されてきました。
依存性注入/ IoCコンテナ
IoCと依存性注入は密接に関連する2つのデザインパターンであり、コンテナーは基本的に、これらのパターンの両方を実行するインフラストラクチャコードのチャンクです。
IoCパターンは、構築の責任を委任することであり、依存性注入パターンは、すでに構築されているオブジェクトに依存関係を提供することです。
これらは両方とも、構築への2段階のアプローチとして扱うことができます。コンテナを使用する場合、コンテナには次のようないくつかの責任があります。
- 要求されたときにオブジェクトを構築します。
- コンテナは、そのオブジェクトが何に依存するかを決定します。
- それらの依存関係を構築します。
- 構築中のオブジェクトにそれらを注入します。
- プロセスを再帰的に実行します。
依存性注入を使用して、ViewModelとクライアントサービス間の分離を解除する方法を見てみましょう。それに関連する依存性注入を使用して、保存処理の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();
}
}
}
保存処理を行う簡単な方法は、AddEditCustomerViewModelにICustomersRepositoryの新しいインスタンスを追加し、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はより緩く分離されています。
[顧客の追加]ボタンを押すと、次のビューが表示されます。ユーザーがフィールドを空のままにすると、フィールドが強調表示され、保存ボタンが無効になります。