接下來,我們來處理 AddQueue
在支援不同類型 Queue 時,要不停增加函數多型的情況。
在這邊,我們回顧一下需求。整理一下思路,程式改動的目的是 不用修改程式,就可以指向不同類型的佇列
。
再加上前面 Enqueue(QueueItem item)
的實作,是利用 Label 的 欄位進行資料的識別,那我們是不是可以加入 Label 做為 registry key 。
Binding Queue 時,必須註冊 RouterKey,以便 Router 在分派輸入的物件。
我們最初的想法,是直接指定 Router 的泛型類別,而且 Queue 的儲存內容,限定泛型類別或其繼承類別。
// 實作概念
public class QueueRouter<T>
{
public void AddQueue(string label, ConcurrentQueue<T> queue){...}
public void Enqueue(QueueItem item){...}
}
還記得先前的 QueueItem
的 Payload
類別是 object
嗎?
在變更 Enqueue(QueueItem item)
的前提下,就會發生隱型限制,也就 T
的類型只能為 object
。但基於一次只變動特定功能的原則。
這個版本的問題,我們延後處理,先驗證目前想法是否可行。
// 變更為泛型的 QueueRouter
public class QueueRouter<T>
{
private Dictionary<string, ConcurrentQueue<T>> _dctQueues
= new Dictionary<string, ConcurrentQueue<T>>();
public void AddQueue(string label, ConcurrentQueue<T> queue)
{
_dctQueues[label] = queue;
}
public void Enqueue(QueueItem item)
{
if (!_dctQueues.ContainsKey(item.Label))
return;
_dctQueues[item.Label].Enqueue((T)item.Payload);
}
}
// 測試案例
[TestClass]
public class QueueRouterTest
{
[TestMethod]
public void QueueRouter_輸入不同的值_放入對應佇列()
{
var router = new QueueRouter<object>();
var intQueue = new ConcurrentQueue<object>();
var strQueue = new ConcurrentQueue<object>();
router.AddQueue(typeof(int).ToString(), intQueue);
router.AddQueue(typeof(string).ToString(), strQueue);
int[] expected = {23, 16};
string[] strExpected = {"Skype", "Google"};
Enqueue2Router(router, expected);
Enqueue2Router(router, strExpected);
var actual = DequeueFromQueue(intQueue);
var strActual = DequeueFromQueue(strQueue);
expected.ToExpectedObject().ShouldMatch(actual.ToArray());
strExpected.ToExpectedObject().ShouldMatch(strActual.ToArray());
}
private static void Enqueue2Router<T1, T2>(
QueueRouter<T1> router, IEnumerable<T2> expected) where T2 : T1
{
foreach (var i in expected)
router.Enqueue(new QueueItem {Label = i.GetType().ToString(), Payload= i});
}
private static List<T> DequeueFromQueue<T>(ConcurrentQueue<T> queue)
{
var actual = new List<T>();
while (!queue.IsEmpty)
{
queue.TryDequeue(out var item);
actual.Add(item);
}
return actual;
}
}
雖然我們確定這個作法可行,但目前會遇到幾點問題。
QueueItem
造成的泛型類別隱性限制。所以,接下來,我們先處理泛型類別隱性限制為 object 的問題。
先前 QeueueRouter 的泛型類別會被隱性限制為 object 的問題,主要是出在 QueueItem 的 Payload 類別。
所以我們將 QueueItem
變更為 QueueItem<T>
,同時對 QueueRouter 進行對應的改動
// QueueItem 的泛型
public class QueueItem<T>
{
public string Label { get; set; }
public T Payload { get; set; }
}
// 實作概念
public class QueueRouter<T>
{
public void AddQueue(string label, ConcurrentQueue<T> queue){...}
public void Enqueue(QueueItem<T> item){...}
}
針對概念的變動,此次迭代的異動範圍有...
QueueItem
→ QueueItem<T>
。// 針對迭代的內容,只要修改這個函數,即可全部通過測試。
private static void Enqueue2Router<T1, T2>(Router<T1> router, IEnumerable<T2> expected) where T2 : T1
{
foreach (var i in expected)
{
router.Enqueue(new QueueItem<T1> {Label = i.GetType().ToString(), Payload = i});
}
}
從上面,我們己經解決了 QeueueRouter 的泛型類別會被隱性限制為 object 的問題 ,但還是幾點可以持續改善的。
Payload
的類型為 Vaule Type 時,用 object
做為泛型類別,仍有 Boxing/Unboxing 的效能問題。目前只有部份滿足需求,就是所有的資料,只要放入同一個 Router,就可以放入對應的 queue 之中。
所以接下來,我們先改善一個 Router 無法通用所有 Queue 類型的問題。