2009-11-12 45 views
10

背景:我有一些类实现了我已经做出线程安全的主题/观察者设计模式。如果observer与通知正在建立在同一线程中,则将通过简单的方法调用observer->Notified(this)来通知它的observers。但是如果observer是在不同的线程中构建的,那么通知将被发布到queue上,稍后由构建observer的线程处理,然后可以在处理通知事件时进行简单的方法调用。帮我删除一个单例:寻找替代方案

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

该地图是一个单身人士。

我过去一直都在使用单身人士,因为“在这个应用程序中只会有一个人”,并相信我 - 我已经付出了我的忏悔!

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

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

过去一直使用的“常规”解决方案是将指针传递给要使用的对象,而不是引用单例。在这种情况下,我认为这样做会很棘手,因为观察者和主题在我的应用程序中是10分钱,并且必须将队列/线程映射对象传递给每个观察者的构造函数非常尴尬。

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

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

谢谢。

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

+2

为什么你想避免单身?他们当然有他们的位置。每一个习语都可能被滥用和滥用。但是thread-> notification_queue的应用程序范围的映射对我来说似乎是合理的。 – Mordachai 2009-11-12 21:46:15

+0

@Mordachai:我知道单身人士有他们的位置,很可能这个队列/线索图是完全有效的。当我编写一些单元测试时,它只是开始出现问题,并且在那里有单例测试时感觉很尴尬。 – 2009-11-12 22:08:52

+0

你正在使用什么线程库? – outis 2009-11-12 22:28:22

回答

0

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

使这个依赖显式化并对其进行控制有什么尴尬?

至于应用工厂MiškoHevery在他的文章中描述的最大优点是1)工厂方法不会隐藏依赖关系和2)您所依赖的单个实例不是全局可用的,因此任何其他对象可以干涉他们的状态。因此,使用这种方法,在任何给定的顶级应用程序环境中,您都可以确切知道地图的用途。通过全球可访问的单身人士,您使用的任何课程都可能会对地图做出不愉快的事情。

+0

意图是(队列/线程机制对于观察者的用户是透明的 - 所有派生的观察者需要知道的是,如果涉及线程化,Notified()方法不一定是同步方法调用。观察者基类很可能取决于地图和通知队列,但我不想(如果我可以帮助它的话)将该依赖关系拖入派生类并强制每个派生的观察者了解队列/线程图。在我的应用程序中有很多(> 500)观察者! (我想这就是你的意思,“控制它”? – 2009-11-12 21:49:55

+0

的确,这就是我的意思!我不是故意穿你的耐心或传道给合唱团,因为你已经表明你不要像单身人士一样 - 但单身人士的透明度只是虚幻的,也就是说,它只有在它失败之前才是透明的。说了这么一句话,如果你有500个派生的观察者类(yike),JS Bangs的想法可能更令人满意 – 2009-11-12 21:51:43

+0

@杰夫:这不一定要超过500个观察者每个主题:-)有许多观察员和许多主题,在不同程度的多对多关系 – 2009-11-13 06:29:07

1

争取解决一些想法:

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

假设你确实有很好的理由保持queue,为什么不把它作为一个实例呢?只要在main的某个地方执行queue = new Queue(),然后传递该参考。可能只有一个,但你仍然可以把它当作一个实例而不是全局静态。

+0

个人而言,当我编写多线程观察者时,我更喜欢我的观察者方法总是在他们自己的线程的上下文中调用。有另一个线程注入它本身的声音只对非常非常精简的情况非常有用。 – Mordachai 2009-11-12 21:50:41

+0

@JS Bangs:我可以看到你对线程安全的看法,但目前的实现是让主题处理互斥锁和线程安全,而不是观察者。现在改变现在对团队来说不会太好! (顺便说一句,它是队列线程映射,它是单例;不是队列 - 有许多队列和线程;一个映射)。我担心的是必须将此引用映射对象传递给每个观察者实例的更改。 – 2009-11-12 21:57:54

+0

@Mordacahai:是的,这是我采取的方法 - 每个观察者的Notified()方法都在它拥有的线程的上下文中运行。有强制异步通知(排队)(在单线程的情况下)或强制同步通知(方法调用)(在多线程的情况下)的机制,但它们很少使用,并且如果你是“高级”意识到后果。 – 2009-11-13 06:33:58

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方法来启动调度......当然这是所有过于简化,但这只是想法。

注:我正在假设你已经有了一个围绕这个模型的体系结构,以便你已经有了一堆观察者,一个或多个主题,并且线程已经去了地图做通知。这摆脱了单身人士,但我建议你从同一个线程通知,并让观察员处理并发问题。

+0

每个线程有一个队列。在一个线程中可以建立许多观察者。此外,许多主题可以从同一个线程通知,因此每个主题有一个队列是矫枉过正的。 – 2009-11-12 22:02:37

0

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

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

+0

我明白你的意思,但我认为这暴露了太多的依赖关系。隐藏派生观察者和主题的队列行为的好处之一是,在线程之间(在源代码中;而不是在运行时)移动观察者变得非常容易,并让他们'自动'知道要使用哪个队列;观察者和主题的用户不必担心该实现细节。 – 2009-11-13 06:26:47

+0

在这种情况下,单身人士可能是你想要的。这没有概念上的问题,因为根据定义,这个过程中只有一组“所有线程”。或者(但不太便携),如果你的平台有这样的事情,你可以把队列放在线程本地存储中。 – 2009-11-13 11:42:23

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 
} 

然后在你的测试中,换出队列/线程映射实现。至少这使得单身人士更加暴露。

+0

啊......有趣。实际上,我已经用这种方式分割出了一些其他的单例来进行测试,所以我可以在本地重用基本部分,并忽略单例部分,但是我没有加入'setInstance'方法。谢谢。 – 2009-11-13 06:20:15

0

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

+0

这是值得思考的事情。我可以将它与Snazzer的“插件式”单例想法结合起来。干杯。 – 2009-11-24 22:54:00