2009-11-12 44 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