2009-11-11 55 views
11

對於我的軟件開發編程類,我們應該爲RSS提要製作一個「Feed Manager」類型的程序。以下是我如何處理FeedItems的實現。封裝變得荒謬嗎?

尼斯和簡單:

struct FeedItem { 
    string title; 
    string description; 
    string url; 
} 

我被下調爲, 「正確」 的答案例子如下:

class FeedItem 
{ 
public: 
    FeedItem(string title, string description, string url); 

    inline string getTitle() const { return this->title; } 
    inline string getDescription() const { return this->description; } 
    inline string getURL() const { return this->url; } 

    inline void setTitle(string title) { this->title = title; } 
    inline void setDescription(string description){ this->description = description; } 
    inline void setURL(string url) { this->url = url; } 

private: 
    string title; 
    string description; 
    string url; 
}; 

現在對我來說,這似乎是愚蠢的。我真的不敢相信我被打倒了,當時我的確有同樣的事情發生,而且我的開銷很大。


這讓我想起了如何在C#中的人總是這樣做:

public class Example 
{ 
    private int _myint; 

    public int MyInt 
    { 
     get 
     { 
      return this._myint; 
     } 
     set 
     { 
      this._myint = value; 
     } 
    } 
} 

我的意思是我GET他們爲什麼這樣做,也許以後他們要驗證在二傳手的數據或在吸氣劑中增加它。但是,爲什麼你們不這樣做呢?UNTIL那種情況出現了嗎?

public class Example 
{ 
    public int MyInt; 
} 

對不起,這是一種咆哮,並不是一個真正的問題,但冗餘令我很生氣。爲什麼當不需要的時候,吸氣人員和安裝人員如此熱愛?

+6

如果你認爲這是荒謬的,你還沒有使用的庫與具有返回單個Manager實例與返回與包含SQL字符串屬性返回類映射類的單一方法的一種方法的單身工廠類聲明。哦,每個中間階層都有一個界面。 – 2009-11-11 02:31:54

+0

如果你這樣做,直到它需要改變它已經太晚了。你有一個成員標題(這是一個字符串)。但有時候我們需要將實現改爲char數組。然後不幸的是,你的實現被卡住了(每個人都已經在使用title(和它的公共API的一部分),你不能改變它們,所以你不能改變你的實現。您實現私有部分不外露,因而是可以改變的。 – 2009-11-11 04:04:29

+7

個人而言,我恨geters/setter方法(它的Java一樣,而不是C++等)。你爲什麼要與對象的內部擺弄,它看起來並不像它是有道理的一旦創建就更改這些成員(但這取決於用法要求)我會讓所有成員都是私有的,構造函數來創建它們並查看對象的使用方式。添加unnesacery getters/setters幾乎與具有公共變量。 – 2009-11-11 04:12:42

回答

14

這是一個「最佳實踐」和風格的問題。

  • 你不想直接暴露數據成員。你總是希望能夠控制它們的訪問方式。在這種情況下,我同意這看起來有點荒謬,但它意在教會你這種風格,以便你習慣它。
  • 它有助於爲類定義一致的接口。你總是知道如何去找東西 - >調用它的get方法。

然後還有可重用性問題。說,在路上,你需要改變當有人訪問數據成員時發生的情況。您可以在不強制客戶端重新編譯代碼的情況下執行此操作。您可以簡單地更改該類中的方法,並確保使用新邏輯。

+1

是的,我明白,標準接口是一個很好的論點。但考慮一下C#的例子,如果需求出現,兩者之間的變化並不難,而且界面保持不變。在我的C++示例中,FeedItem永遠不會需要額外的開銷,它只是保存一些數據的一種便捷方式,而不是根本就不採取行動。 – y2k 2009-11-11 02:19:20

+1

也許關於你的C#示例,不過對於你的C++示例,你正在做出假設。良好軟件工程的關鍵在於設計代碼,以便在假設發生變化時輕鬆更改代碼。儘管看起來有點荒謬,但說實話,在這種情況下,我認同你,可能沒有必要,對你的代碼有哪些假設可能會改變?也許這些網址是以相對的方式提供給你的,但你的代碼假設它們是絕對的,改變這個類比改變使用它的3k程序容易得多:-) – 2009-11-11 02:23:16

+0

這通常不是什麼大問題。如果您確實需要更改某人訪問數據成員時發生的情況,則仍然可以更改該類接口以使其成爲私有成員並提供getter方法。然後編譯器會指出你需要修改代碼的其餘部分。沒有太多的工作。 – StackedCrooked 2009-11-11 02:29:48

13

下面是關於這個問題的一個很好的討論:Why use getters and setters

你要問自己的問題是:「這是怎麼回事,當你意識到FeedItem.url確實需要進行驗證,從現在的情況發生3個月但它已經直接從其他287類引用?」

+1

那麼在這種情況下,我在存儲URL之前驗證了URL。該結構只是保存數據的一種便捷方式,而不是從內部執行。感謝那個鏈接太順口了。 – y2k 2009-11-11 02:20:48

+0

您應該考慮(以面向對象的方式)誰(哪個類)負責驗證URL。我認爲FeedItem類驗證URL是合理的,因爲它是唯一可以保證URL始終有效的類。 – 2009-11-11 02:39:22

+1

每個人都說要驗證URL會錯過RSS提要的要點。沒有必要驗證網址!!!!!!!!!!只需顯示供稿發佈者說的內容,如果供稿發佈者上的網址無效,而不在我身上。 – y2k 2009-11-11 09:26:41

7

在需要之前完成此操作的主要原因是版本控制。

字段的行爲與屬性不同,特別是在將它們用作左值時(通常不允許,尤其是在C#中)。此外,如果您需要,稍後添加屬性獲取/設置例程,則會破壞您的API - 您的類的用戶需要重寫其代碼以使用新版本。

預先做這件事更安全。

C#3,順便說一句,這使得更容易:

public class Example 
{ 
    public int MyInt { get; set; } 
} 
+0

確實。在C#中,創建屬性和創建公共字段一樣簡單,所以不妨做正確的事。 – 2010-06-18 13:45:40

+0

但是在C++中,你實際上可以寫屬性,可以作爲左值使用。看[這裏](http://stackoverflow.com/a/3634540/531179)一個例子。 – ulidtko 2012-03-19 03:48:33

0

我同意你的看法,但對系統生存是很重要的。在學校時,假裝同意。換句話說,被降低對你是有害的,不值得爲你的原則,意見或價值而加以標記。

此外,在一個團隊或僱主工作時,假裝同意。之後,開始你自己的事業並按你的方式去做。當你嘗試別人的方式時,要冷靜地對他們開放 - 你可能會發現這些經歷重新塑造了你的觀點。

在內部實現發生變化的情況下,封裝理論上是有用的。例如,如果每個對象的URL成爲計算結果而不是存儲值,那麼getUrl()封裝將繼續工作。但我懷疑你已經聽到了這一面。

+3

_「在學校時,假裝同意,並且在工作時......假裝同意,然後開始自己的事業並按照自己的方式行事」_下一句應該是「......和__然後___你會發現爲什麼其他人都是這樣做的。「 – 2009-11-11 02:20:57

+1

嗯,我不感興趣。有可能OP有一點關於封裝的問題,有時候過於執行。 – 2009-11-11 02:21:30

+3

衷心不同意。如果你正在和一位教授合作,讓你認定你對簡單的好處的看法 - 找一位不同的教授。如果你正在和一個不願意接受簡單好處的僱主一起工作,那麼找一個不會讓你陷入糾結路徑的僱主來調試。只要確保你始終準備好從討論中找出那個 - 你是錯的。 – Kzqai 2009-11-11 03:57:24

4

我絕對同意你的看法。但在生活中,你應該做正確的事情:在學校,這是爲了得到好的分數。在你的工作場所,它是完成規格。如果你想要固執,那很好,但是要自己解釋一下 - 在評論中掩蓋你的基礎,以儘量減少你可能受到的傷害。

在上面的特定示例中,我可以看到您可能想要驗證URL,即URL。也許你甚至想要對標題和描述進行消毒,但是無論哪種方式,我認爲這是你在課堂設計中早期可以告訴的事情。在評論中說明你的意圖和理由。如果你不需要驗證,那麼你不需要一個getter和setter,你是完全正確的。

簡單支付,這是一個有價值的功能。從不虔誠地做任何事情。

+0

當在羅馬,入鄉隨俗做:) – StackedCrooked 2009-11-11 02:25:31

+1

有時候,這是在拍一部關於一類設計決定一個顯著的因素。只要有意識就可以了。有時候這只是錯誤的,這種羅馬式的做事方式就是事情失控的原因。然後你簡化事情並解釋你自己。有人可能會用蝙蝠擊中你,你必須糾正自己,但我認爲整個過程對於系統來說依然健康。 – wilhelmtell 2009-11-11 02:29:51

1

作爲一名C++開發人員,我只是爲了保持一致性而將我的成員永遠保密。所以我總是知道我需要輸入p.x(),而不是p.x.

另外,我通常避免實現setter方法。而不是改變一個對象,我創建一個新的:

p = Point(p.x(), p.y() + 1); 

這也保留了封裝。

+0

Eew。拿那個,約定。 – 2009-11-11 11:41:57

+2

對於不可變的值類型+1! – Tom 2009-11-12 05:27:19

1

絕對有一點封裝變得荒謬。

引入代碼中的抽象越多,您的前期教育就越多,學習曲線的代價就會越大。

每個人都知道C可以調試一個可怕的書面1000線功能,只使用基本的C語言標準庫。不是每個人都可以調試你發明的框架。每個引入的級別封裝/抽象都必須權衡成本。這並不是說它不值得,但是一如既往,你必須爲你的情況找到最佳平衡點。

+0

避免不必要的複雜性是一個有趣的說法,但它並沒有站出來反對的反對意見認爲,getter和setter提出一個很常見的界面和幾乎所有的程序員都熟悉它。 – ulidtko 2012-03-19 04:03:32

3

也許這兩個選項都有點不對,因爲這兩個版本都沒有任何行爲。沒有更多的上下文很難進一步評論。

http://www.pragprog.com/articles/tell-dont-ask

現在讓我們想象一下,你的FeedItem類已成爲奇妙流行,到處都是正在使用的項目。您決定需要(如其他答案所建議的)驗證已提供的網址。

快樂的日子裏,你已經寫了一個URL的setter。你編輯這個,驗證URL並且拋出一個異常,如果它是無效的。你釋放你的新版本,每個使用它的人都很高興。 (讓我們忽略檢查與未檢查的異常,以保持這一點)。

除非你接到憤怒的開發者的電話。他們在應用程序啓動時正在從文件中讀取一個feeditems列表。而現在,如果有人在配置文件中犯了一個小錯誤,那麼就會拋出新的異常,並且整個系統無法啓動,只是因爲一個frigging feed項錯誤!

您可能已經保持方法簽名相同,但是您已更改接口的語義,因此它打破了相關的代碼。現在,您可以選擇高地並告訴他們重新編寫他們的程序,或者虛心地添加setURLAndValidate

+1

好點:如果這只是一個POD類,封裝的價值會大大減少。 – itowlson 2009-11-11 02:34:26

+1

也許,而不是舉行一個字符串,URL本身應該是驗證自己的內容,因此,無論是FeedItem也不是這個類的任何客戶端將需要驗證它的類的實例。如果他們已經被給了一個URL實例,那麼他們知道他們可以依賴它的有效性,因爲它在實例構建時會被檢查。這就是我如何將接近它 - 一個URL/URI不只是一個POD,它有嚴格的語法和可能提供這樣的方法爲getScheme()的gethostname(),getQueryString()等 – 2010-03-30 23:29:32

1

軟件行業面臨的問題之一是可重用代碼的問題。這是一個很大的問題。在硬件世界中,硬件組件被設計一次,然後當您購買組件並將它們放在一起製造新事物時,設計將被重複使用。

在軟件世界裏,每當我們需要一個組件時,我們一次又一次地設計它。它非常浪費。

作爲確保創建的模塊可重複使用的技術,提出了封裝。也就是說,有一個明確定義的接口,可以抽象出模塊的細節,並且可以在稍後使用該模塊。該界面還可以防止對象的誤用。

您在課堂上構建的簡單類沒有充分說明對定義良好的界面的需求。說:「但是,爲什麼你們不這樣做,直到出現這種情況?」將不會在現實生活。你在軟件工程課程中學到的東西是設計其他程序員可以使用的軟件。考慮到如.net框架和Java API提供的庫的創建者完全需要這樣的規範。如果他們認爲封裝太麻煩了,這些環境幾乎不可能合作。

遵循這些準則將在未來產生高質量的代碼。爲該領域增添價值的代碼,因爲不僅僅是你自己將從中受益。

最後一點,封裝還可以充分測試一個模塊併合理確定它的工作原理。沒有封裝,測試和驗證你的代碼會更困難。

+1

這似乎是一個反儘管如此,原型製作視圖。 – Kzqai 2009-11-11 03:52:08

+0

@Tchalvak不一定。目標是實現良好的封裝類。沒有理由不能以增量的方式來完成。 – 2009-11-11 04:17:52

4

如果事情是一個簡單的結構,那麼是可笑的,因爲它只是數據。

這實際上僅僅是OOP開始的倒退,人們仍然沒有得到類的想法。沒有理由有數以百計的獲取和設置方法,以防萬一您可能將getId()有一天改爲對哈勃望遠鏡的遠程調用。

你真的想在TOP級別的功能,在底部它是毫無價值的。 IE你會有一個複雜的方法被髮送一個純虛擬類來處理,保證它仍然可以工作,不管下面發生什麼。只要將它隨機放置在每個結構中就是一個笑話,並且它不應該爲POD完成。

+0

但是在適當的OOP方法中,你可能永遠不會使用結構。您似乎認爲很少需要額外的代碼才能訪問成員,但事實並非如此。 底線,你的IDE可以自動生成默認的getter/setter和編譯器可以優化出來。編寫這個評論花費的時間比在10個類上自動創建getter/setter更長。 – 2009-11-11 11:40:06

+3

「適當的面向對象方法」的問題是並非所有的數據都是對象。有時候他們只是...數據。一些信息本身沒有任何行爲,但被系統的其他部分使用。如果它不是標量值,它必須是某種聚合結構,它將我們置於POD結構中。 – Tom 2009-11-12 05:29:14

1

吸氣劑/固化劑當然是很好的做法,但是他們的寫作很枯燥,甚至更糟糕。

有多少次我們讀了一個有六個成員變量和伴隨的getter/setter的類,每個類都有完整的hog @ param/@ return HTML編碼,像'get the value of X'這樣着名的無用評論'設置X'的值,'獲取Y的值','設置Y的值','獲取Z的值','設置Zzzzzzzzzzzzz的值。撲通!

+0

你不需要寫它們。獲得體面的IDE或插件。 – 2009-11-11 11:43:26

+5

@John:如果你的getters和setter非常愚蠢,你的IDE可以爲你寫它們,那麼你只是證明了原來的海報的重點。明確的獲取者和設置者的整個概念是根本上有缺陷的,只要人類可能儘快從軟件工程中清除。 – 2009-11-12 03:35:17

3

記住編碼「最佳實踐」,往往是由編程語言的發展而過時。

例如,在C#中的getter/setter概念已經被烤成在屬性的形式語言。通過引入自動屬性,C#3.0使這更容易,編譯器自動爲您生成getter/setter。 C#3.0還引入了對象初始化器,這意味着在大多數情況下,您不再需要聲明只是初始化屬性的構造器。

所以規範C#的方式做你正在做的事情是這樣的:

class FeedItem 
{ 
    public string Title { get; set; } // automatic properties 
    public string Description { get; set; } 
    public string Url { get; set; } 
}; 

和使用應該是這樣的(使用對象初始化):

FeedItem fi = new FeedItem() { Title = "Some Title", Description = "Some Description", Url = "Some Url" }; 

點是你應該試着學習最好的做法或規範的做法是針對你正在使用的特定語言,而不是簡單地複製那些不再有意義的舊習慣。

+0

+1。雖然C++在語言中沒有屬性語法(它們可以用一些模板進行模擬,但仍然可以),但是Bjarne似乎完全拒絕了它的需要,這是非常可惜的。 – ulidtko 2012-03-19 03:53:52

1

這是一個很常見的問題:「爲什麼你不人只是這樣做,直到這種情況出現?」。 原因很簡單:通常稍後不修復/重新測試/重新部署會便宜得多,但要在第一時間正確執行。 舊的估計表明,維護成本是80%,大部分的維護正是你所說的:只有在有人遇到問題時才做正確的事情。第一次做對,可以讓我們專注於更有趣的事情,並提高生產力。

草率的編碼通常是非常無利可圖 - 你的客戶不滿,因爲該產品是不可靠的,他們不是生產時使用它。開發人員也不高興 - 他們花了80%的時間來做補丁,這很無聊。最終,你最終會失去客戶和優秀的開發人員。