访问外部聚合类的最佳实践

本文关键字:最佳 外部 访问 | 更新日期: 2023-09-27 18:03:10

我目前正在开发一个WPF应用程序,它有一个主窗口,其中包含一个面板,其内容可以交换以显示不同的用户控件。基本上,这只是意味着我有一个带有经常可见的侧栏的菜单。

我的问题是关于减少类之间的耦合。我有一个通知部分(在一个列表框的形式)在我的主窗口,消息可以弹出。通知可以由某些用户控件(UCs)中发生的操作"触发"。这些用户控件当然是主窗口类的成员。

由于我需要影响主窗口类中的ListBox控件,我想知道最佳实践将是什么。显然,最简单的解决方案是通过每个UCs的构造函数将对Main Window的引用传递给它们,但这在很多层面上似乎不是很有效。当然,我只能传递ListBox元素,但这也不能真正起作用,因为我必须对添加到ListBox中的数据执行操作,因此我必须在每个UC中以编程方式重复这些指令。

我可以使用一个引用ListBox并实现通知方法的单例,但是我已经在我的项目中有了更多我满意的单例(我不希望每个人都能访问这些方法,只有某些UCs)。

另一种方法是通过相关UCs的构造函数传递Notification Manager实例(它将完成相同的工作)。该管理器将依次拥有将UCs链接到主窗口的所有必要方法。

可能有我没有想到的其他更有效的解决方案。我想知道您对这种情况下的最佳实践的看法,特别是考虑到它生成的类耦合。它可能与这个特定的问题有关,但是要以更一般的方式考虑它,其中内部聚合类需要访问外部的一些资源。谢谢。

访问外部聚合类的最佳实践

我喜欢Jesse提出事件的想法:它非常符合wpf,并且允许需要记录内容的UserControl和提供日志记录的窗口之间的松耦合。此外,做日志记录的UserControls不依赖于日志服务是否可用,这将使解决方案更加灵活。

这是如何工作的

需要记录日志的UserControl只是引发一个冒泡事件,其中包含相关信息(要记录的文本消息)。UC将不知道该消息将如何处理,甚至不知道该消息是否将被处理,它只是提出它。这是一种轻量级的操作,耦合最小。

凸起的消息沿着层次结构链向上冒泡,一直到顶层元素(Window, Page)。在此过程中的任何地方,元素都可以为此事件类型提供处理程序,并通过它获得有关日志请求的通知。同样,耦合是非常松散的:实现的地方并不关心谁发送消息。可以是UserControl或者其他任何东西

如果UIElement可用作日志消息事件的起源,则此操作绝对有效。

这里是一步一步的实现:

LogServices类

我们不能为了这个目的重用一个已经存在的事件,需要创建一个新的事件(为了避免混淆)。应该这样做:

// Subclass of RoutedEventArgs that allows us to easily and nicely pass the message
// to be logged
public class LogEventArgs : RoutedEventArgs
{
    public string Msg { get; set; }
    public LogEventArgs(RoutedEvent routedEvent, Object sender, string Msg)
        :base(routedEvent, sender)
    {
        this.Msg = Msg;
    }
}
// This is the Delegate that's used to grab the Log message
public delegate void RoutedLogEventHandler(Object sender, RoutedEventArgs e);
// This works as the abstraction layer that will allow UC's to raise LOG messages
// and allow your implementation to alter the way it handles those LOG messages.
// Since we're doing this with a routed event, we need an DependencyObject to
// reigster it.
public class LogServices :UIElement
{        
    public static RoutedEvent LogEvent;
    // Static constructor, registers the event
    static LogServices()
    {
        LogEvent = EventManager.RegisterRoutedEvent("Log", RoutingStrategy.Bubble, typeof(RoutedLogEventHandler), typeof(UIElement));
    }
    // This helps raise the relevant shared event
    public static void RaiseLog(string Msg, UIElement sender)
    {
        sender.RaiseEvent(new LogEventArgs(LogEvent, sender, Msg)); 
    }
}

上面的代码声明了RoutedEventArgs的子类,因为我们需要传递字符串Message。然后,它创建新的委托类型,该类型接受我们的LogEventArgs参数,最后从LogServices的静态构造函数注册事件。

如何发送事件

发送事件超级简单。这是按钮的Click处理程序:

LogServices.RaiseLog("Message to be logged.", sender as Button)

接收事件

我们的事件被注册为"冒泡"事件:它将从我们提出它的控件开始,并给每个父节点一个处理它的机会,一直到Window。最简单的方法是在执行日志记录的Window中处理事件。不幸的是,这种类型的共享不能直接从XAML中设置,它需要从代码中分配。这就是我如何尝试从XAML分配它,它没有工作:

<Grid local:LogService.Log="HandlerName" />

下一个选项是从代码中分配它。这是我的测试Window1的一个片段:

    // Window Constructor
    public MainWindow()
    {
        InitializeComponent();
        // Set the event handler.
        base.AddHandler(LogServices.LogEvent, new RoutedLogEventHandler(HandleMsgLog));
    }
    // This is the actual handler.
    public void HandleMsgLog(Object sender, RoutedEventArgs e)
    {
        // Put the received message into the ListBox
        LB.Items.Add((e as LogEventArgs).Msg);
    }

你可以阅读观察者模式(http://en.wikipedia.org/wiki/Observer_pattern),但这种模式比你的情况更广泛使用。

在你的情况下,我认为好的变体是:

1)另一种方法是通过相关UCs的构造函数传递Notification Manager实例(该实例将执行相同的工作)。该管理器将依次拥有将UCs链接到主窗口的所有必要方法。(c)

2)所有UC必须实现带有'NotificationRaised'等事件的接口,并且在主窗口中需要订阅此事件。Jesse在对你的问题的评论中说道