MVP三元组之间的通信

6

好的,朋友们,例如我之前告诉你们的这个表格只有一个DockContent在DockPanel中

自那时以来,我做了一个编辑,每个项目中的单词都会在用户点击左侧面板上的某个项目时出现。我很容易地为左侧窗格创建了一个三元组。

它有项目presenter、项目视图、项目模型。这是每个东西的接口:

interface IProjectsModel
{
    void AttachPresenter(IProjectsModelObserver observer);
    System.Collections.Generic.List<Project> projects { get; }
    Project selectedProject { get; set; }
}

public interface IProjectsViewObserver
{
    void UserChangedSelectedProject(Project project);
}
public interface IProjectsModelObserver
{
    void SelectedProjectChanged(Project project);
}

public interface IProjectsView : IView
{
    List<Project> projects { set; }
    Project project { set; }

    void AttachPresenter(IProjectsViewObserver presenter);
}

目前,我在考虑为右窗格创建全新的MVP三合一模式。

但这不是主要问题。我遇到的主要问题是如何在上面提到的MVP三元组之间进行沟通处理?

我在网上阅读了一些文章,其中说在这种情况下需要引入一些模型协调器到项目中。

所以,我的问题是:

  1. 我是否正确地做出了两个三元组而不是一个?
  2. 如何协调两个三元组之间的关系?
  3. 如果您有任何有用的建议、意见、倡议、建议或提示,我们将非常感激!

非常感谢您的关注和时间!

4个回答

2
为了协调演示者之间的交流,您可以让您的MainForm类实现IProjectsViewObserver接口,并在用户选择不同项目时设置右侧停靠窗格上的文本。例如:
class MainForm : IProjectsViewObserver
{
    void UserChangedSelectedProject(Project project)
    {
          rightDockPane.setText(project.getText());
    }
}

如果您想测试这种行为,可以创建一个单独的类:
class DockPaneProjectUpdater : IProjectsViewObserver

虽然在这种情况下这是不必要的,因为代码非常简单。

你可以使用匿名方法来研究更简单的方式,但我对C#了解不足,无法提供帮助。

我应该做两个三元组而不是一个,这样做是对的吗?

是的,因为dock和pane是独立的组件,它们很可能需要单独的MVP triad。您可以尝试通过使代码可重用和可测试来确定MVP triads的粒度(小)。

如何协调两个三元组之间的关系?

创建一个窗口级别的presenter来连接子presenter,并能够在三元组之间协调行为。 您可以将窗口presenter设置为IProjectsViewObserver并在DockContent上采取行动。或者,如果您真的想将行为模块化/单元测试,则可以为三元组之间的通信创建单独的presenter类(通常是匿名类)。

您有任何建议/提议/意见/提示等其他对我有用的东西吗?

阅读Wikipedia和有关MVP和presenter-first 的在线文章。 MVP最大的好处是可测试性和行为(presenter)模块化,因此请确保通过单元测试(通常使用mock/DI框架)并将行为重构为MVP triads以便在可以重用代码时利用它们。

要想有效地应用MVP模式,需要一些时间,所以请耐心!


为什么这个窗口呈现器经常是匿名类? - kseen
行为由于其与交互的特定性而无法重复使用,但仍然需要实现一个接口。因此,匿名类是一个很好的选择。 - Garrett Hall
你能给我展示一些代码来说明这个选项吗?我有点困惑。 - kseen
谢谢!我还可以请你回答我在问题中指定的三个问题点吗? - kseen
已在上面回答了,如果您还有问题,请告诉我。 - Garrett Hall

1

如果你想要实现完全薄和抽象的三元交互方式,你可以使用中介者模式。创建一个“消息”类/结构,一个演示者将订阅它,另一个演示者将发送它。

这种方法与订阅事件非常相似,但提供了更多的抽象,因为双方的Presenter甚至不知道他们的消息接收者是否存在。由于您正在一个进程的上下文中工作,因此可以在消息中传递任何您想要的内容,甚至是回调委托。

中介者模式实现的一个很好的例子可以在MVVM Light框架中找到。它被称为Messenger。看看其中一个测试

个人而言,当需要在没有共同父级或创建者的对话框之间发送信息时,我使用中介者模式。在那种情况下,它有助于简化事情。


正如您所提到的“...因为双方的演示者甚至不知道他们的消息的接收者是否存在...”。事件难道不是也是这样吗? - kseen
这几乎是相同的,但在调用事件之前,您应该检查它是否为空,并且这假定事件仅在订阅者存在时触发。 - Alexander Manekovskiy

1

遵循Garrett的建议。创建一个主控制器(masterpresenter)和相互了解并可以相应行动的子控制器(subpresenters)。最简单的方法是在两个面板的子控制器中创建属性。

您来自Java世界吗? :) C#有自己的观察者模式实现:events。请查看一下,这样您就不需要额外的类似于Java的接口了。

更新:当我开始写作时,我改变了我的想法。在我看来,控制器之间进行通信的最佳方式是使用共享模型。

class SelectedProjectChangedEventArgs : EventArgs
{
    public Project SelectedProject {get;set;}
}

class Project
{

}

interface IReadOnlyModel
{
    Project SelectedProject {get;} 
    event EventHandler<SelectedProjectChangedEventArgs> SelectedProjectChanged;
}

interface IWritableModel
{
    Project SelectedProject {get;set;} 
    IList<Project> Projects {get;}
}

class Model : IReadOnlyModel, IWritableModel
{
    public Project SelectedProject {get;set;} 
    public event EventHandler<SelectedProjectChangedEventArgs> SelectedProjectChanged;
    public IList<Project> Projects {get;set;}
}

class ProjectsListPresenter
{
    readonly IWritableModel _model;

    public ProjectsListPresenter(IWritableModel model)
    {
        _model = model;
    }

    public void ChangeSelectedProject(Project project)
    {
        _model.SelectedProject = project;
    }
}

class ProjectDetailsPresenter
{
    readonly IReadOnlyModel _model;

    public ProjectDetailsPresenter(IReadOnlyModel model)
    {
        _model = model;
        _model.SelectedProjectChanged += ModelSelectedProjectChanged;
    }

    void ModelSelectedProjectChanged(object sender, SelectedProjectChangedEventArgs e)
    {
        //update right pane
    }
}


class WholeFormPresenter
{
    public ProjectDetailsPresenter DetailsPresenter {get;private set;}
    public ProjectsListPresenter ListPresenter {get;private set;}

    public WholeFormPresenter(Model model)
    {
        DetailsPresenter = new ProjectDetailsPresenter(model);
        ListPresenter = new ProjectsListPresenter(model);
    }
}

class WholeForm
{
    ListBox _projectsListControl;
    Panel _detailsPanel;
    public WholeForm()
    {
        var presenter = new WholeFormPresenter(new Model());
        _projectsListControl.Presenter = presenter.ListPresenter;
        _detailsPanel.Presenter = presenter.DetailsPresenter;
    }
}

我知道类/接口的名称不是很完美,但我希望这个想法是清晰的。


您可以指向一些相关的代码示例吗?如果您能亲眼看到实践中的真正代码,就会更容易理解。回答中的另一个段落。不,我是纯C#开发者,从未接触过Java。我只是觉得事件机制有些奇怪。请考虑查看我的此处关于MVP模式下如何在Presenter和View之间进行通信的另一个问题:http://stackoverflow.com/questions/10561233/how-do-you-communicate-between-presenter-and-view-in-mvp-scheme#comment13675969_10561233 - kseen
不仅要使用接口而不是事件,而且对于公共方法要使用驼峰式命名法而不是帕斯卡式命名法。请参考:http://msdn.microsoft.com/en-us/library/ms229043.aspx - dzendras
你还在返回主页吗? :) - kseen
这是我最喜欢的ATB歌曲:D - kseen
我不认识他们 ;) 请阅读我的更新并说出你的想法。我无法将自己的思维方式转化为类似Java的事件处理方式。 - dzendras
我还重写了我的代码,使用内置的事件机制。传说中的Jeremy Miller犯了个错 :( - kseen

0
我创建了一个名为EventHub的静态类,它可以注册“侦听器”并发布新事件。我自己在C#方面还是个新手,所以肯定有更好的方法来改进这个类:
public enum EventType
{
    // Your event types
}

public static class EventHub
{
    static Dictionary<EventType, List<Delegate>> _eventHandlers;

    // Use this to fire events with input
    public static void Publish( EventType eventType, object data )
    {
        // Fire event handler if found
        if( _eventHandlers.ContainsKey( eventType ) )
        {
            _invokeHandlers( _eventHandlers[ eventType ], data );
        }
    }

    static void _invokeHandlers(List<Delegate> list, object data)
    {
        if( !(list != null && list.Count > 0) )
            return;

        foreach( var handler in list )
            handler.DynamicInvoke( data );
    }

    // Use this to register new "listeners"
    public static void Register( EventType eventType, Delegate handler )
    {
        // Init dictionary
        if( _eventHandlers == null )
            _eventHandlers = new Dictionary<EventType, List<Delegate>>();

        // Add handler
        if( _eventHandlers.ContainsKey( eventType ) )
            _eventHandlers[ eventType ].Add( handler );
        else
            _eventHandlers.Add( eventType, new List<Delegate> { handler });
    }

    // Use this to remove "listeners"
    public static void Dismiss( EventType eventType, Delegate handler )
    {
        // Nothing to remove
        if( _eventHandlers == null || _eventHandlers.Count == 0 )
            return;

        // Remove handler
        if( _eventHandlers.ContainsKey( eventType ) && _eventHandlers[ eventType ].Count > 0 )
            _eventHandlers[ eventType ].Remove( handler );

        // Remove Key if no handlers left
        if( _eventHandlers[ eventType ].Count == 0 )
            _eventHandlers.Remove( eventType );
    }
}

使用方法:

void MyHandler(object data) { /*...*/ }
EventHub.Register( EventType.MyType, MyHandler );
//...
EventHub.Publish( EventType.MyType, "I am data, I can be anything" );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接