Ответ 1
Вот простой пример, демонстрирующий концепцию пассивных представлений с использованием шаблона проектирования MVP. Поскольку мы используем пассивные взгляды, представление не имеет представления о ведущем. Ведущий будет просто подписаться на события, опубликованные в представлении, и действовать соответственно.
Для начала нам нужно определить контракт для нашего представления. Обычно это достигается с помощью интерфейса, по сути, мы хотим иметь очень свободную связь с нашим представлением. Мы хотим, чтобы возможность переключения на разные виды или события создавала макетные представления для модульного тестирования.
Вот контракт, который описывает простой вид, который будет использоваться для отображения информации о клиенте.
public interface ICustomerManagementView
{
void InitializeCustomers(ICustomer[] customers);
void DisplayCustomer(ICustomer customer);
event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}
Он предоставляет единственный метод InitializeCustomers, который будет использоваться для инициализации нашего представления объектами из нашей модели.
У нас также есть событие SelectedCustomerChanged, которое будет использоваться нашим презентатором для получения уведомления о том, что в представлении произошло действие.
Как только у нас будет наш контракт, мы можем начать обрабатывать эти взаимодействия в нашем ведущем.
public class CustomerManagementPresenter
{
private ICustomer _selectedCustomer;
private readonly ICustomerManagementView _managementView;
private readonly ICustomerRepository _customerRepository;
public CustomerManagementPresenter(ICustomerManagementView managementView, ICustomerRepository customerRepository)
{
_managementView = managementView;
_managementView.SelectedCustomerChanged += this.SelectedCustomerChanged;
_customerRepository = customerRepository;
_managementView.InitializeCustomers(_customerRepository.FetchCustomers());
}
private void SelectedCustomerChanged(object sender, EventArgs<ICustomer> args)
{
// Perform some logic here to update the view
if(_selectedCustomer != args.Value)
{
_selectedCustomer = args.Value;
_managementView.DisplayCustomer(_selectedCustomer);
}
}
}
В презентаторе мы можем использовать другой шаблон проектирования, называемый инъекцией зависимостей, чтобы обеспечить доступ к нашему представлению и любым классам моделей, которые могут нам понадобиться. В этом примере у меня есть CustomerRepository, который отвечает за получение информации о клиенте.
В конструкторе у нас есть две важные строки кода, во-первых, мы подписались на событие SelectedCustomerChanged в нашем представлении, здесь мы можем выполнять связанные действия. Во-вторых, мы вызвали InitilaizeCustomers с данными из репозитория.
На этом этапе мы фактически не определили конкретную реализацию нашего представления, все, что нам нужно сделать, это создать объект, реализующий ICustomerManagementView. Например, в приложении Windows Forms мы можем сделать следующее
public partial class CustomerManagementView : Form, ICustomerManagementView
{
public CustomerManagementView()
{
this.InitializeComponents();
}
public void InitializeCustomers(ICustomer[] customers)
{
// Populate the tree view with customer details
}
public void DisplayCustomer(ICustomer customer)
{
// Display the customer...
}
// Event handler that responds to node selection
private void CustomerTreeViewAfterSelect(object sender, TreeViewEventArgs e)
{
var customer = e.Node.Tag as ICustomer;
if(customer != null)
{
this.OnSelectedCustomerChanged(new EventArgs<ICustomer>(customer));
}
}
// Protected method so that we can raise our event
protected virtual void OnSelectedCustomerChanged(EventArgs<ICustomer> args)
{
var eventHandler = this.SelectedCustomerChanged;
if(eventHandler != null)
{
eventHandler.Invoke(this, args);
}
}
// Our view will raise an event each time the selected customer changes
public event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}
Если бы мы хотели проверить нашу логику представления, мы могли бы издеваться над нашим представлением и выполнять некоторые утверждения.
EDIT: Включенные пользовательские аргументы событий
public class EventArgs<T> : EventArgs
{
private readonly T _value;
public EventArgs(T value)
{
_value = value;
}
public T Value
{
get { return _value; }
}
}