帮我删除一个Singleton:寻找替代方案

时间:2009-11-12 21:24:14

标签: c++ design-patterns singleton

背景:我有一些实现主题/观察者设计模式的类,我已经使其成为线程安全的。 subject如果observers是在与通知相同的线程中构建的,则observer->Notified( this )将通过简单的方法调用observer通知observer。但是如果queue是在不同的线程中构建的,那么通知将被发布到observer以便稍后由构造{{1}}的线程处理,然后简单的方法调用可以在处理通知事件时进行。

所以...我有一个关联线程和队列的映射,在构造和销毁线程和队列时会更新。此映射本身使用互斥锁来保护对其的多线程访问。

地图是单身人士。

我过去曾因使用单身人士而感到内疚,因为“这个应用程序中只有一个”,并相信我 - 我付出了忏悔!

我的一部分不禁想到应用程序中确实只有一个队列/线程映射。另一个声音说单身人士不好,你应该避免他们。

我喜欢删除单例并能够为我的单元测试存根的想法。麻烦的是,我很难想出一个好的替代解决方案。

过去工作的“通常”解决方案是传入指向要使用的对象的指针,而不是引用单例。我认为在这种情况下这将是棘手的,因为在我的应用程序中观察者和主题是10-a-penny,并且将队列/线程映射对象传递给每个观察者的构造函数是非常尴尬的。

我欣赏的是,我的应用程序中可能只有一张地图,但它不应该在主题和观察者类代码的内容中做出决定。

也许这是一个有效的单身人士,但我也很感激如何删除它。

感谢。

PS。我已在接受的答案中提及What's Alternative to Singletonthis article。我不禁想到ApplicationFactory它只是另一个名字的另一个单身。我真的没有看到优势。

6 个答案:

答案 0 :(得分:3)

如果尝试摆脱单例的唯一目的是从单元测试的角度来看,可能用可以在存根中交换的东西替换单例getter。

class QueueThreadMapBase
{
   //virtual functions
};

class QeueueThreadMap : public QueueThreadMapBase
{
   //your real implementation
};

class QeueueThreadMapTestStub : public QueueThreadMapBase
{
   //your test implementation
};

static QueueThreadMapBase* pGlobalInstance = new QeueueThreadMap;

QueueThreadMapBase* getInstance()
{
   return pGlobalInstance;
}

void setInstance(QueueThreadMapBase* pNew)
{
   pGlobalInstance = pNew
}

然后在您的测试中,只需换出队列/线程映射实现。至少这会让单身人士多一点。

答案 1 :(得分:1)

对解决方案的一些想法:

为什么需要为在不同线程上创建的观察者排队通知?我的首选设计是让subject直接通知观察者,并将责任放在观察者身上,以便在线程安全地实现自己,并且知道Notified()可能随时被另一个人调用线。观察者知道他们的状态的哪些部分需要用锁来保护,他们可以比subjectqueue更好地处理。

假设您确实有充分理由保留queue,为什么不将其作为实例?只需在queue = new Queue()中的main处进行{{1}},然后传递该引用。可能只有每一个,但您仍然可以将其视为实例,而不是全局静态。

答案 2 :(得分:1)

将队列放入主题类有什么问题?你需要什么地图?

您已经从单例队列映射中读取了一个线程。而不是这样做只需在主题类中创建地图并提供两种方法来订阅观察者:

class Subject
{
  // Assume is threadsafe and all
  private QueueMap queue;
  void Subscribe(NotifyCallback, ThreadId)
  {
     // If it was created from another thread add to the map
     if (ThreadId != This.ThreadId)
       queue[ThreadId].Add(NotifyCallback);
  }

  public NotifyCallBack GetNext()
  {
     return queue[CallerThread.Id].Pop;
  }
}

现在任何线程都可以调用GetNext方法来开始调度......当然它已经过度简化了,但这只是想法。

注意:我正在假设您已经拥有围绕此模型的架构,以便您已经拥有一堆观察者,一个或多个主题,并且线程已经转到映射来做通知。这摆脱了单例,但我建议你从同一个线程通知并让观察者处理并发问题。

答案 3 :(得分:0)

你的观察者可能很便宜,但他们依赖于通知 - 队列 - 线程图,对吗?

让这种依赖性明确并控制它有什么尴尬?

至于应用工厂MiškoHevery在他的文章中描述,最大的优点是:1)工厂方法不隐藏依赖关系; 2)您依赖的单个实例不是全局可用的,因此任何其他对象都可以干涉他们的国家。因此,使用这种方法,在任何给定的顶级应用程序上下文中,您都可以准确地知道使用地图的内容。使用全局可访问的单例,您使用的任何类都可能使用或对地图进行令人讨厌的事情。

答案 4 :(得分:0)

我的方法是让观察者在注册主题时提供队列;观察者的所有者将负责线程和相关队列,并且主体将观察者与队列相关联,而不需要中央注册表。

线程安全的观察者可以在没有队列的情况下注册,并由主体直接调用。

答案 5 :(得分:0)

如何添加一个Reset方法,将单例返回到初始状态,可以在测试之间调用?这可能比存根更简单。可以将一般的Reset方法添加到Singleton模板(删除内部单例pimpl并重置指针)。这甚至可以包括所有单身人士的注册表,其中有一个主ResetAll方法来重置所有这些单身!