Pregunta

usando MVP, cuál es el orden normal de construcción y la inyección de dependencia.

normalmente crea un presentador para cada vista y pasa la vista al presentador en el constructor. Pero qué pasa si tienes:

  1. Un servicio en el que varias vistas necesitan escuchar eventos.
  2. Múltiples vistas todas apuntando a la misma caché de modelo de datos.

¿alguien puede mostrar un flujo normal de información de un usuario? Haga clic en los datos que regresan en un servicio desde un servidor.

¿Fue útil?

Solución

Esto es lo que hago:

Primero, defino estas interfaces:

public interface IView<TPresenter>
{
    TPresenter Presenter { get; set; }
}

public interface IPresenter<TView, TPresenter>
    where TView : IView<TPresenter>
    where TPresenter : IPresenter<TView, TPresenter>
{
    TView View { get; set; }
}

Entonces esta clase de presentador abstracto:

public abstract class AbstractPresenter<TView, TPresenter> : IPresenter<TView, TPresenter>
    where TView : IView<TPresenter>
    where TPresenter : class, IPresenter<TView, TPresenter>
{
    protected TView view;

    public TView View
    {
        get { return this.view; }
        set
        {
            this.view = value;
            this.view.Presenter = this as TPresenter;
        }
    }
}

La vista se inyecta a través de una propiedad, en lugar del constructor, para permitir el afecto bidireccional en el setter. Tenga en cuenta que se necesita un yeso seguro ...

Entonces, mi presentador concreto es algo así como:

public class MyPresenter : AbstractPresenter<IMyView, MyPresenter>
{
    //...
}

Donde IMyView implementa IView . Debe existir un tipo de vista concreta (por ejemplo, MyView ), pero es el contenedor el que lo resuelve:

  1. Registro el tipo MyPresenter como sí mismo en el contenedor, con un comportamiento transitorio.
  2. Registro MyView como IMyView en el contenedor con un comportamiento transitorio.
  3. Luego solicito un MyPresenter al contenedor.
  4. Contenedor instanciar un MyView
  5. Instancia un MyPresenter
  6. Inyecta la vista en el presentador a través de la propiedad AbstractPresenter.View .
  7. El código de establecimiento completa la asociación bidireccional
  8. El contenedor devuelve la pareja Presentador / Vista

Le permite inyectar otras dependencias (servicios, repositorios) tanto en su vista como en su presentador. Pero en el escenario que describió, le recomiendo que inyecte servicios y cachés en el presentador , en lugar de la vista.

Otros consejos

En WinForms, prefiero un enfoque simple. Por lo general, se trata de algunos UserControls en una superficie de diseño: haga de estas sus clases de vista. .NET crea la jerarquía de control para usted (a través de InitializeComponent). Si utiliza el patrón Vista pasiva , cada vista crea una instancia de su presentador. (Puede hacerlo directamente o preguntando a un contenedor IOC). Use la inyección del constructor para pasar una referencia a la interfaz de la vista al constructor del presentador. El presentador puede conectarse para ver los eventos. Repita el proceso para el modelo: el presentador crea una instancia de un modelo y conecta sus eventos. (En este caso, no necesita la inyección del constructor ya que la Vista pasiva dice que el presentador mantiene una referencia al modelo, no al revés).

El único inconveniente que he encontrado con este enfoque es administrar adecuadamente las vidas del modelo y presentador. Desea mantener la vista lo más simple posible, por lo que probablemente no desee que mantenga una referencia al presentador. Sin embargo, eso significa que tiene este objeto presentador dando vueltas con controladores de eventos vinculados a su vista. Esta configuración evita que su vista sea recogida de basura. Una solución es que su vista publique un evento que indique que se está cerrando. El presentador recibiría el evento y eliminaría sus suscripciones de modelo y vista. Los objetos en su web ahora están correctamente desreferenciados y el recolector de basura puede hacer su trabajo.

Terminas con algo como lo siguiente:

public interface IView
{
   ...
   event Action SomeEvent;
   event EventHandler Disposed;
   ...
}

// Note that the IView.Disposed event is implemented by the 
// UserControl.Disposed event. 
public class View : UserControl, IView
{
   public event Action SomeEvent;

   public View()
   {
      var presenter = new Presenter(this);
   }
}

public interface IModel
{
   ...
   event Action ModelChanged;
   ...
}

public class Model : IModel
{
   ...
   public event Action ModelChanged;
   ...
}

public class Presenter
{
   private IView MyView;
   private IModel MyModel;

   public Presenter(View view)
   {
      MyView = view;
      MyView.SomeEvent += RespondToSomeEvent;
      MyView.Disposed += ViewDisposed;

      MyModel = new Model();
      MyModel.ModelChanged += RespondToModelChanged;
   }

   // You could take this a step further by implementing IDisposable on the
   // presenter and having View.Dispose() trigger Presenter.Dispose().
   private void ViewDisposed(object sender, EventArgs e)
   {
      MyView.SomeEvent -= RespondToSomeEvent;
      MyView.Disposed -= ViewDisposed;
      MyView = null;

      MyModel.Modelchanged -= RespondToModelChanged;
      MyModel = null;
   }
}

Puede desacoplar este ejemplo un paso más si usa IOC y solicita a su contenedor IOC implementaciones de IModel (en la clase Presenter) e IPresenter (en la clase View).

interface IEmployee
{
    int EmployeeId {get;}
    string FirstName {get;}
    string LastName {get;}
}
interface IEmployeeRepository
{
    void SaveEmployee(IEmployee employee);
    IEmployee GetEmployeeById(int employeeId);
    IEmployee[] Employees { get; }
}
interface IEmployeeView
{
    event Action<IEmployee> OnEmployeeSaved;
}

interface IEmployeeController
{
    IEmployeeView View {get;}
    IEmployeeRepository Repository {get;}
    IEmployee[] Employees {get;}        
}

partial class EmployeeView: UserControl, IEmployeeView
{
    public EmployeeView()
    {
        InitComponent();
    }
}
class EmployeeController:IEmployeeController
{
    private IEmployeeView view;
    private IEmployeeRepository repository;
    public EmployeeController(IEmployeeView view, IEmployeeRepository repository)
    {
        this.repository = repository;
        this.view = view;
        this.view.OnEmployeeSaved+=new Action<IEmployee>(view_OnEmployeeSaved);
    }

    void  view_OnEmployeeSaved(IEmployee employee)
    {
        repository.SaveEmployee(employee);
    }
    public IEmployeeView View 
    {
        get
        { 
            return view;
        }
    }
    public IEmployeeRepository Repository
    {
        get
        {
            return repository;
        }
    }

    public IEmployee[] Employees
    {
        get 
        {
            return repository.Employees;
        }
    }
}

WinformsMVP es un muy buen marco MVP para formularios de Windows. Puede inyectar fácilmente un servicio en múltiples vistas fácilmente usando este marco. Este es un buen artículo con un código fuente de muestra explica cómo usar el marco.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top