我需要一种单例模式的设计替代方案。

9
我知道有很多讨论关于单例模式以及为什么它们不好。但这个问题并不是关于这个的。我了解使用单例模式的缺点。
我有一个场景,使用单例模式很容易且看起来很合理。但是,我想要一个替代方案,能够在不增加太多开销的情况下实现我所需要的功能。
我们的应用程序被设计成客户端,通常在现场的笔记本电脑上运行,并与后端服务器通信。我们在主应用程序底部有一个状态栏。它包含几个文本区域,显示各种状态和信息,以及几个图标。这些图标会改变它们的图像以指示它们的状态。例如,GPS图标表示是否连接以及错误状态。
我们的主类名为MobileMain。它拥有状态栏区域,并负责创建它。然后我们有一个StatusBarManager类。StatusBarManager目前是一个静态类,但也可以是单例模式。以下是该类的开头。
public static class StatusBarManager
{
    static ScreenStatusBar StatusBar;

    /// <summary>
    /// Creates the status bar that it manages and returns it.
    /// </summary>
    public static ScreenStatusBar CreateStatusBar()
    {
        StatusBar = new ScreenStatusBar();
        return StatusBar;
    }

MobileMain会向StatusBarManager请求一个StatusBar,然后使用该StatusBar。其他类不会看到StatusBar,只有StatusBarManager能够看到。
状态栏的更新可以来自应用程序中的任何地方。大约有20个类可以更新状态栏上的文本区域,还有其他类可以更新图标状态。
将只有一个StatusBar和一个StatusBarManager。
有关更好的实现方式的任何建议?
我想过一些想法:
使StatusBarManager成为实例类。在我的MobileMain类中,保持对StatusBarManager类的静态公共实例的引用。然后要进行状态栏更新,您将调用MobileMain.StatusBarManager.SetInformationText或管理器的某些其他方法。StatusBarManager将不是一个单例,但MobileMain只会创建它的静态实例。问题在于MobileMain现在拥有一个StatusBar和一个StatusBarManager,仅管理其拥有的StatusBar。仍然需要全局可用的静态StatusBarManager实例,只是不同的所有者。
另一个想法是使用像EventEggregator类这样的东西。我从未使用过,但已经了解过。我猜想的概念是它将是一个全局可用的类。在每个想要更新状态栏的类中,它都会发布一个StatusBarUpdate事件。StatusBarManager将是订阅StatusBarUpdate事件的唯一类,并接收所有通知。我已经读过了,如果在清理对象时不小心取消订阅事件,则可能会出现泄漏。这种方法值得研究吗?

1
这篇文章中有许多问题。请逐个提出以获得答案。 - N_A
一篇关于StatusBarManager命名的好文章;http://www.codinghorror.com/blog/2006/03/i-shall-call-it-somethingmanager.html - Patrick
1
单例模式并不总是不好的,如果你有单例模式所要解决的问题,请使用它。然而,它已经被贬低了,因为人们试图在单例模式无法解决的问题上使用它,这会导致任何设计模式的灾难。 - Andy
Patrick,谢谢,我们经常使用SomethingManager。在某些情况下,由于该类执行多个操作,因此使用它是合适的,但也许我们应该将该类分解为更专业化的类。我将重命名我们的类为StatuBarUpdater,因为它只负责更新状态栏。 - WPFNewbie
@WPFNewbie 对于你的第一个观点表示肯定。需要认真思考如何将所有这些事件抽象出来,以便StatusBarManager可以以相同的方式处理它们(尽管响应方式不同)。对于你的第二个观点也是肯定的,但是EventAggregator将允许您打破StatusBarManager和20多个状态更新器之间的紧密耦合。您可以使用DI将事件聚合器实现(作为由DI容器管理的单例)注入到StatusBarManager和20多个更新器中。 - MattDavey
显示剩余3条评论
5个回答

3
我更喜欢持有对象的静态类。因此,您可以通过静态类提供的接口来访问对象的数量受到限制。只要您的应用程序仍然可扩展,静态并不是坏事。
单例模式的另一个好替代品是单状态模式,其中有一个类实现了私有静态字段以表示“单例”行为。
请参见:
单状态模式
单状态模式 vs. 单例模式 更新: 将REST API视为内部程序结构常常会对我有所帮助。拥有一个从任何地方更新并向每个人发送通知的类很难控制,涉及竞争条件和无限循环(更新->事件->更新->...)。
构建一个(静态或非静态)状态栏界面,您可以在需要时访问它。通过静态类,在其中获取对状态栏界面的访问权限,或通过依赖注入(如果使用此类技术)(不推荐用于较小的项目)。每次调用状态栏界面都必须独立于可能由状态栏引发的任何事件,以避免进一步出现引发条件问题。将状态栏界面视为可以从程序的其他部分调用以推送和拉取信息的网站。

所以,简要概括一下,您认为我们当前的设计师中StatusBarManager是一个静态类没有任何问题吗?我查看了您提供的其他链接。至于单例模式,问题在于我不希望它是透明的。每个人都应该知道只有一个状态栏,这就是他们正在更新的内容。这是我的感觉。我对静态类和单例模式的主要问题是引用它们的类的可重用性。现在,您已经引用了一个全局类,如果您在另一个项目中重用该类,则必须满足该类。 - WPFNewbie
通常静态类的“坏处”在于,当您有许多类依赖于某些静态类时,这会使得重构和测试变得不可能。如果您的状态栏而不是与状态栏的接口是静态的,那么状态栏仍然可以用于测试。静态类将足够小,以便根据需要进行重写。 - Tarion
如果你想要摆脱静态类,我只看到依赖注入(属性或构造函数注入)是可能的选择。构造函数注入将需要在每个构造函数中使用StatusBar(或StatusBarHandler),这可能会太多了。属性注入将依赖于你注入属性的事实。这通常由MicroKernel完成。整个程序将依赖于该内核。如果你需要可测试的代码,DI是一个好方法,但在较小的项目中它可能会让你感到困惑,因为它更难调试。 - Tarion
对于在MobileMain中包含一个静态引用StatusBarManager类的实例的建议有何评论?一些更新状态栏的类也是单例,比如我们的CommunicationManager,它负责在与服务器通信时更新传输图标状态。对于DI,我是否应该在CommunicationManager中拥有一个StatusBarManager属性,然后在MobileMain中使用命令CommunicationManager.StatusBarManager = _statusBarManager,其中_statusBarManager现在表示MobileMain拥有的管理器实例? - WPFNewbie
不要从MobileMain静态链接它,而是可以使StatusBarManager中的Function/Property静态。这样,您就不会获得额外的依赖项和更多的责任,这对于无法很好地扩展的MobileMain来说是不利的。如果通过MobileMain通过StatusBarManager访问StatusBar,您将获得什么优势?DI的工作方式与您描述的类似,但通常使用属性和/或容器更动态地实现,其中注册实例(如StatusBar)。然后,MobileMain将配置容器,例如:_container.RegisterSingleton<StatusBarManager>(); - Tarion

2
有没有一个 StatusBar 类或 StatusBarManager 类并不是什么大问题。但是许多应用程序中有很多类都知道 StatusBars 和 StatusBarManagers,这是个坏主意,会导致强耦合,并且某一天可能会带来烦恼。
为什么呢?
想象一下,当前报告状态到状态栏的组件需要在另一个应用程序中被重用: - 需要使用文本控制台来报告状态? - 报告状态到多个地方? 或者 - 没有报告状态!
最佳替代方法: -事件监听。在您的类上公开一个状态更改事件(可以使用回调),或者在您的类共有的现有共享资源上公开此事件。其他利益相关方,如状态栏,可以订阅该事件,并且在不再需要/有效时取消订阅,以防止泄漏,正如您所提到的那样!
-由于您已经标记了 WPF,请使用依赖属性“StatusText”,这似乎是另一个诱人的选择。采用这种方法时,当您有多个状态属性时,您需要找出哪个属性正在告诉您现在需要显示在状态栏上的最有趣的状态!这可以是绑定、多绑定(复杂),或依赖属性更改事件处理程序。
然而,我建议您尽可能将 DependencyObjects 和 DependencyProperties 限制在 UI 层中。原因是它们隐含地依赖于 UI 线程上的调度程序,因此很难为非 UI 工作进行适应。
由于您的应用程序有许多不同的部分,可能还可以发现在某些地方使用这两种方法的组合是合理的。

1
感谢您的回答。我想澄清一下我对您最佳方法的理解。我们应该在每个类中放置一个StatusChanged事件来更新状态栏。然后引发该事件,状态栏将订阅它。这是您建议的吗?如果是这样,StatusBar或StatusBarManager必须了解每个更新它的类,才能订阅StatusChanged事件。我确实理解所有类都需要了解StatusBarManager以实现可重用性的问题,这也是我想要进行更改的部分原因。 - WPFNewbie
我也考虑使用NotificationManager,这可能属于您共享资源的建议。它可以处理所有通知,包括状态栏。理想情况是有一个接口,这样只要有一个实现了INotifier接口的类,这些类就可以在任何项目中重复使用,但我认为单例或静态类无法做到这一点。 - WPFNewbie
啊 - 这是一个很好的观点 - StatusBar 实际上不需要知道每个类!相反,您可以拥有一个 StatusBar.UpdateStatus() 方法,然后在更高层次的类(由于其他原因已经了解所有内容),进行一些绑定以使所有事件处理程序调用该方法。 - Tim Lovell-Smith

1
您可以简单地使用观察者模式,并将 StatusBar 添加为 20 个对象的监听器。这将消除单例并更好地遵循 SRP 和 DIP,但是您需要考虑是否值得努力。如果间接性增加了太多复杂性并且不可能进行依赖注入,则单例可能更好。
public class StatusBar implements StatusListener {
}

public interface StatusListener {
   public statusChanged(String newStatus)
}

0

类将隐式依赖于任何使用单例模式的实例,并显式依赖于构造函数中的任何参数。我建议为单例模式添加一个接口,这样只有需要的方法才会对使用 IStatusBar 的类暴露出来。这会增加一些代码量,但会方便单元测试。


-1

不了解您的应用程序架构很难给出建议,但也许您应该考虑使用依赖注入。例如,将一个 StatusBar 实例传递给直接使用它的每个类的构造函数。


听起来有点困难。现在你必须在构造时向所有对象传递额外的参数,在构造函数中添加额外的代码来初始化它们,在各个地方的对象中添加额外的字段来存储对实际上是单个对象的多个引用,而且所有类型都不能再没有 StatusBar 类型的情况下编译。 - Tim Lovell-Smith

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