2008-10-14 105 views
27

我正在使用C#編寫項目。之前的程序員並不知道面向對象的編程,所以大部分代碼都在大文件中(我們正在討論大約4-5000行),它們遍佈數十個甚至數百個方法,但只有一個類。重構這樣一個項目是一項巨大的任務,因此我現在已經學會了如何使用它。使用靜態方法vs實例化包含方法的類的性能

每當在其中一個代碼文件中使用某個方法時,該類將被實例化,然後在該對象實例上調用該方法。

我在想這樣做是否有明顯的表現處罰?我應該讓所有的方法都是「現在」靜態的,最重要的是,應用程序會以任何方式從中獲益嗎?

+0

我認爲這應該被遷移到CS.SE – 2016-02-29 15:20:14

回答

24

here開始,每次調用實例方法時,靜態調用比構造實例的速度快4到5倍。然而,我們仍然只談到每次調用幾十納秒,因此除非您有非常緊密的循環調用方法數百萬次,否則您不可能注意到任何好處,並且可以通過在外部構造單個實例來獲得相同的好處該循環並重新使用它。

由於您必須更改每個呼叫站點以使用新的靜態方法,因此您可能最好花時間逐步重構。

+0

真棒文章。我剛開始這樣做是因爲我對更快的事情感興趣。 「雖然object.read」或For-Loop和IF。這篇文章是我個人研究之後的完美之作。非常好。現在我有一個非常大的對象,它在許多地方被訪問,我想知道它的價值是通過方法將它傳遞給它必須去的地方,或者只是創建一個全局變量類並在那裏訪問它。 很難測試什麼會更快......? :( – 2016-04-25 14:36:24

2

對我來說,創建一個對象似乎很愚蠢,所以你可以調用一個看起來對對象沒有副作用的方法(從你的描述中我假設這一點)。在我看來,更好的折衷辦法是擁有幾個全局對象並使用它們。這樣,您可以將通常爲全局的變量放入適當的類中,以使它們的範圍略小。

從那裏你可以慢慢地移動這些對象的範圍變得越來越小,直到你有一個體面的OOP設計。

然後再次,可能會使用的方法是不同的;)。我個人很可能會關注結構和對它們進行操作的函數,並嘗試將這些結構和成員一點一點地轉換成類。

至於問題的性能方面,靜態方法應該稍微快一點(但不多),因爲它們不涉及構造,傳遞和解構對象。

3

我想你已經用你問的方式部分回答了這個問題:你有沒有任何明顯的性能處罰?

如果處罰不明顯,你不一定要做任何事情。 (儘管不言而喻,代碼庫將從漸進重構變成可敬的OO模型而受益匪淺)。

我想我說的是,性能問題只是一個問題,當你發現它是一個問題。

7

它取決於該對象包含的內容 - 如果「對象」只是一堆函數,那麼它可能不是世界的盡頭。但是,如果對象包含一堆其他對象,那麼實例化它將調用它們的所有構造函數(以及當它被刪除時的析構函數),並且您可能會得到內存碎片等等。

這就是說,這聽起來不像是目前性能是你最大的問題。

9

我處理了一個類似的問題,我工作。我之前的程序員創建了一個控制器類,其中所有BLL函數都被轉儲。

我們現在正在重新設計系統,並根據它們應該控制的內容創建了許多Controller類。

UserController中,GeographyController,ShoppingController ...

裏面他們有靜態方法,這使得調用緩存或使用Singleton模式的DAL每個控制器類。

這給了我們2個主要優勢。速度稍快(大約快2-3倍,但在這裏說話納秒; P)。另一種是,代碼更乾淨

ShoppingController.ListPaymentMethods() 

,而不是

new ShoppingController().ListPaymentMethods() 

我覺得很有道理使用靜態方法或類,如果該類不保持任何狀態。

5

您必須確定重寫的目標。如果你想有很好的可測試性,可擴展的&可維護的OO代碼,那麼你可以嘗試使用對象及其實例方法。畢竟這是面向對象的編程,我們在這裏討論,而不是面向類的編程。

當您定義實現接口的類並執行實例方法時,僞造和/或模擬對象是非常直接的。這使得徹底的單元測試變得快速有效。另外,如果你要遵循良好的OO原則(參見SOLID http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29)和/或使用設計模式,你肯定會做很多基於實例的,基於接口的開發,而不是使用許多靜態方法。

至於這個建議:

看來愚蠢我創建一個對象,所以您可以調用的 看似有對象沒有副作用的方法(從你的描述我認爲這)。

我看到很多點網商店和我這違反了封裝,一鍵OO概念。我不應該通過該方法是否是靜態的來判斷一個方法是否有副作用。除了破壞封裝外,這意味着如果/當您修改它們以產生副作用時,您將需要將方法從靜態更改爲實例。我建議你閱讀這篇關於Open/Closed原理的文章,看看上面提到的建議方法是如何處理這個問題的。

請記住,老栗子,'過早優化是萬惡的根源'。我認爲在這種情況下,這意味着在使用不恰當的技術(即面向類的編程)之前,不要跳過這些環節,直到你知道你有性能問題。即使那麼調試問題並尋找最合適的。

2

靜態方法速度更快,使用的內存也更少。有這種誤解,它只是一點點快。只要你不把它放在循環中,它會更快一點。順便說一句,有些循環看起來很小,但實際上並不是因爲包含循環的方法調用也是另一個循環。您可以分辨執行渲染功能的代碼。不幸的是,在許多情況下,內存少得多。一個實例允許與姐妹方法輕鬆共享信息。靜態方法會在需要時詢問信息。

但是就像駕駛汽車一樣,速度帶來了責任。靜態方法通常比實例對象具有更多參數。因爲一個實例會照顧緩存共享變量,所以你的實例方法看起來更漂亮。

ShapeUtils.DrawCircle(stroke, pen, origin, radius); 

ShapeUtils.DrawSquare(stroke, pen, x, y, width, length); 

VS

ShapeUtils utils = new ShapeUtils(stroke,pen); 

util.DrawCircle(origin,radius); 

util.DrawSquare(x,y,width,length); 

在這種情況下,當實例變量被大部分的時間都用的方法,例如方法是很值得的。實例不是關於狀態,而是關於共享,儘管共享狀態是共享的一種自然形式,但它們不是同一個。一般的經驗法則是:如果方法與其他方法緊密結合 - 他們彼此相愛,以至於當他們被叫時,另一個人也需要被調用,他們可能共享同一杯水 - - ,它應該作爲實例。將靜態方法轉換爲實例方法並不困難。您只需要獲取共享參數並將其作爲實例變量。另一種方式更難。

或者你可以創建一個代理類來橋接靜態方法。儘管理論上看來效率可能更低,但實踐卻告訴我們一個不同的故事。這是因爲無論何時需要調用DrawSquare一次(或循環),都可以直接使用靜態方法。但是,無論何時你要一起反覆使用DrawCircle,你將使用實例代理。一個例子是System.IO類FileInfo(instance)vs File(static)。

靜態方法是可測試的。事實上,比實例更容易測試一次。 GetSum(x,y)方法不僅可以測試單元測試,還可以進行負載測試,集成測試和使用測試。實例方法適用於單元測試,但對於其他所有測試都很糟糕(這比單元測試更重要),這就是爲什麼我們現在有這麼多的錯誤。使所有方法無法測試的參數是沒有意義的參數(Sender s,EventArgs e)或像DateTime.Now這樣的全局狀態。事實上,靜態方法在可測試性方面非常出色,以至於在新的Linux發行版的C代碼中看到的錯誤要少於普通的OO程序員(他充滿了我知道的s ***)。

相關問題