2009-02-02 297 views
67

我一直認爲大的switch語句是OOP設計不好的一個症狀。過去,我已經閱讀了討論這個主題的文章,他們提供了基於OOP的替代方法,通常基於多態來實例化正確的對象來處理案例。大開關語句:壞OOP?

我現在處於一種基於來自TCP套接字的數據流的怪異開關語句的情況,其中協議基本上由換行符終止命令組成,後面是數據行,後面是結束標記。該命令可以是100個不同的命令之一,所以我想找到一種方法將這個怪物開關語句減少到更易於管理的地步。

我已經做了一些Google搜索以找到我記得的解決方案,但令人遺憾的是,谷歌已經成爲近期多種查詢無關結果的荒原。

這種問題是否有任何模式?對可能的實現有什麼建議?

我以爲有一個想法是使用字典查找,將命令文本與要實例化的對象類型進行匹配。這具有很好的優點,只需創建一個新對象並在表中插入一個新的命令/類型以用於任何新命令。

但是,這也有類型爆炸的問題。我現在需要100個新類,再加上我必須找到一種方法將它們乾淨地連接到數據模型。 「一個真正的轉換聲明」真的要走嗎?

我會感謝您的想法,意見或評論。

+0

如何使用表達式?所以找到它的名字並調用它的功能。名稱可以很容易地與傳遞的變量匹配,所以我們不需要使用開關。 – leegod 2016-12-28 04:09:13

回答

31

您可能從Command Pattern中獲得一些好處。

對於OOP,如果行爲變化足夠小,您可以將幾個類似的命令摺疊成一個類,以避免完整的類爆炸(是的,我可以聽到OOP大師已經尖叫過) 。但是,如果系統已經是OOP,並且100多個命令中的每一個都是真正獨特的,那麼就讓它們成爲唯一的類並利用繼承來鞏固常見的東西。

如果系統不是OOP,那麼我不會爲此添加OOP ...您可以通過簡單的字典查找和函數指針輕鬆使用Command Pattern,甚至可以基於命令動態生成函數調用名稱,取決於語言。然後,您可以將邏輯上相關的函數分組到代表類似命令集合的庫中以實現可管理的分離。我不知道這種實現是否有很好的術語......我始終認爲它是基於MVC方法處理URL的「調度員」風格。

+0

同意詞典和委託理念。您可能還會通過大型開關語句使用Dictionary 來提高性能。難點在於初始化它,但如果需要,可以使用反射來自動執行。 – Zooba 2009-02-03 01:18:08

+0

也許可以從配置中獲取初始化。 – davogones 2009-02-03 02:53:20

+0

反思的問題在於許多Web主機在信任級別運行,不允許您使用它:( – 2009-02-03 08:34:32

0

有一種方法,我看到你可以改善,這將使你的代碼驅動的數據,所以例如每個代碼匹配處理它(功能,對象)的東西。您也可以使用反射來映射表示對象/函數的字符串,並在運行時解析它們,但您可能需要做一些實驗來評估性能。

2

我看到了戰略模式。如果我有100種不同的策略......就這樣吧。巨型開關語句很醜陋。所有的命令都是有效的類名嗎?如果是這樣,只需使用命令名稱作爲類名稱並使用Activator.CreateInstance創建策略對象。

3

我認爲這是少數情況下大交換機是最好的答案,除非一些其他的解決方案出現自己的情況之一。

23

我看到有兩個 switch語句作爲non-OO設計的一個症狀,其中enum類型的開關可能被替換爲提供抽象接口的不同實現的多個類型;例如,下面...

switch (eFoo) 
{ 
case Foo.This: 
    eatThis(); 
    break; 
case Foo.That: 
    eatThat(); 
    break; 
} 

switch (eFoo) 
{ 
case Foo.This: 
    drinkThis(); 
    break; 
case Foo.That: 
    drinkThat(); 
    break; 
} 

...也許應該被改寫爲...

IAbstract 
{ 
    void eat(); 
    void drink(); 
} 

class This : IAbstract 
{ 
    void eat() { ... } 
    void drink() { ... } 
} 

class That : IAbstract 
{ 
    void eat() { ... } 
    void drink() { ... } 
} 

然而,一個 switch語句 IMO如此強烈的指示switch語句應該用其他內容替換。

+8

問題是你不知道你是否通過網絡接收到一個This或者那個命令,你需要決定是否調用new This()或new That(),然後用OO是一塊蛋糕 – 2009-02-03 08:32:18

0

處理這個特定問題的最佳方法是:序列化和協議乾淨利落地使用IDL並用switch語句生成封送處理代碼。因爲無論您試圖使用哪種模式(原型工廠,命令模式等),您都需要初始化命令ID /字符串和類/函數指針之間的映射,但不知何故它會比開關語句運行得慢,因爲編譯器可以爲開關語句使用完美的哈希查找。

+0

我很好奇編譯器使用完美的散列法實現了字符串的開關語句嗎? – paxos1977 2009-02-03 04:20:47

16

該命令可以是100個不同的命令

一個如果你需要做一個出來的100個不同的東西,你不能避免有100路分支。您可以在控制流(開關,if-elseif^100)或數據(從字符串到命令/工廠/策略的100元素映射)中對其進行編碼。但它會在那裏。

你可以嘗試將100路分支的結果與不需要知道結果的事物分開。也許只有100種不同的方法是好的;沒有必要發明你不需要的東西,如果這使得代碼變得笨重。

+8

+1。我沮喪地聽到人們抱怨交換機很臭,但是後來又提出了一種替代方案,那就是抽象的交換行爲在整個系統中被抹掉,而不是整齊的堆棧。 – 2013-09-18 19:00:39

0

是的,我認爲大型病例陳述是一個症狀,可以改善他的代碼...通常通過實施更面向對象的方法。例如,如果我發現自己評估switch語句中類的類型,那幾乎總是意味着我可能會使用泛型來消除switch語句。

0

您也可以在這裏採用語言方法,並用語法中的相關數據定義命令。然後您可以使用生成器工具來解析語言。爲此我使用了Irony。或者,您可以使用解釋器模式。

我認爲目標不是建立最純粹的OO模型,而是要創建一個靈活,可擴展,可維護和強大的系統。

1

我想說問題不是大開關語句,而是包含在其中的代碼激增,以及濫用錯誤的範圍變量。

我自己在一個項目中經歷過這種情況,當越來越多的代碼進入交換機,直到它變得無法維護。我的解決方案是定義一個參數類,其中包含命令的上下文(名稱,參數,無論在開關之前收集的任何內容),爲每個case語句創建一個方法,並使用該case中的參數對象調用該方法。

當然,一個完全面向對象的命令調度器(基於幻如反射或機制,如Java激活)更漂亮,但有時你只是想解決的事情,並完成工作;)

2

有兩種在談到大型開關語句時想到的東西:

  1. 它違反了OCP - 您可能會不斷維護一個大功能。
  2. 您可能表現不佳:O(n)。

另一方面,地圖實現可以符合OCP並且可能執行O(1)。

1

您可以使用字典(如果您使用Java進行編碼,也可以使用哈希表)(Steve McConnell稱之爲表驅動開發)。

0

我最近有一個巨大的switch語句類似的問題,我得到的最簡單的解決一個查找表和函數或方法返回你所期望的價值驅除掉了醜陋的開關。命令模式是很好的解決方案,但有100個班級我覺得不好。 所以我有這樣的事情:

switch(id) 
    case 1: DoSomething(url_1) break; 
    case 2: DoSomething(url_2) break; 
    .. 
    .. 
    case 100 DoSomething(url_100) break; 

,我已經改變了:

string url = GetUrl(id); 
DoSomthing(url); 

使用getURL可以去DB和返回你正在尋找的網址,也可以是內存中的字典拿着100個網址。 我希望這可以幫助任何人在那裏更換巨大的怪異開關語句。

0

想想Windows最初是如何寫入應用程序消息泵的。它吸了。您添加的菜單選項越多,應用程序運行速度越慢。隨着搜索的命令越來越接近switch語句的底部,響應的等待時間越來越長。長時間切換語句是不可接受的。我做了一個AIX守護進程作爲POS命令處理程序,它可以處理256個獨特的命令,而不必知道通過TCP/IP接收到的請求流中的內容。流的第一個字符是一個函數數組的索引。任何未使用的索引都設置爲默認消息處理程序;記錄並說再見。