類與封裝

細(xì)胞是構(gòu)成生物體不可分割的基本組成單位。細(xì)胞通過(guò)細(xì)胞膜(細(xì)胞壁),劃出了自己清晰的邊界。在邊界內(nèi)部,細(xì)胞有自己的各種物質(zhì)。而細(xì)胞膜則控制著允許外界通過(guò)的物質(zhì)。

細(xì)胞
細(xì)胞

classOOP的地位,像細(xì)胞一樣,構(gòu)成了一個(gè)OO程序的基本組成單位,并具有不可分割性。一個(gè)類內(nèi)部由多種元素組成。其中一部分元素,設(shè)計(jì)者是不希望外部能夠訪問(wèn)的;因而類則必須具備細(xì)胞膜同樣的性質(zhì):對(duì)包含的各種元素進(jìn)行訪問(wèn)控制,由此定義明確而清晰的邊界。這就是類的封裝encapsulation)。

封裝的目的

一提起封裝,程序員們首先想到的是對(duì)類成員的privatepublic控制。但這些都是手段。相對(duì)于手段,目的才更為重要。

應(yīng)用OO的正確姿勢(shì),是將類當(dāng)做一種模塊化手段。而好的模塊劃分原則是高內(nèi)聚,低耦合。(參見(jiàn)《正交設(shè)計(jì),OO與SOLID》)因而,如果你不能很好的理解內(nèi)聚耦合,你就很難通過(guò)OO方法學(xué)得到好的設(shè)計(jì)(事實(shí)上,如果不懂內(nèi)聚耦合,你用任何方法學(xué)都無(wú)法針對(duì)一個(gè)復(fù)雜系統(tǒng)給出良好的設(shè)計(jì))。

高內(nèi)聚強(qiáng)調(diào)的是:關(guān)聯(lián)緊密的事物應(yīng)該被放到一起。這樣有助于將不穩(wěn)定的、易于一起變化的元素都控制在一個(gè)模塊內(nèi)部。從而做到局部化影響

低耦合則強(qiáng)調(diào):API的定義要讓客戶與之的耦合盡可能小,讓客戶代碼難以受到變化影響。

封裝的目的正是為了隔離變化。通過(guò)對(duì)客戶提供抽象接口,隱藏實(shí)現(xiàn)細(xì)節(jié),讓客戶代碼只能依賴于更穩(wěn)定的抽象,與易于變化的實(shí)現(xiàn)細(xì)節(jié)進(jìn)行隔離,從而讓客戶代碼更加穩(wěn)定(向著穩(wěn)定方向依賴)。

如果不能做到這一點(diǎn),你即便使用了再多的private權(quán)限控制,也無(wú)法改善軟件的內(nèi)部質(zhì)量。

數(shù)據(jù)與算法

我們都知道,無(wú)論你采取何種范式,從宏觀角度,程序最終都是由如下兩部分構(gòu)成:

  1. 數(shù)據(jù)
  2. 算法

可是,如果我們對(duì)于程序的理解僅僅停留在這個(gè)層面,那么就很難設(shè)計(jì)出易于應(yīng)對(duì)變化的軟件。

不幸的是,從我多年做咨詢的經(jīng)歷來(lái)看,絕大部分系統(tǒng)的工程師和設(shè)計(jì)師,對(duì)于設(shè)計(jì)的理解都還限于這個(gè)層面,不管他們講的多么天花亂墜,圖畫(huà)的有多漂亮,但最終的代碼會(huì)揭開(kāi)謊言,告訴你一切真相。

讓我們看看,如果僅僅將程序看做數(shù)據(jù)算法,并將它們清晰分離,系統(tǒng)會(huì)呈現(xiàn)的局面:

數(shù)據(jù)與算法分離
數(shù)據(jù)與算法分離

而具體到一個(gè)數(shù)據(jù)結(jié)構(gòu),需要訪問(wèn)數(shù)據(jù)的客戶代碼與其之間的依賴關(guān)系則如下:

算法對(duì)數(shù)據(jù)的耦合
算法對(duì)數(shù)據(jù)的耦合

數(shù)據(jù)結(jié)構(gòu)在很多時(shí)候,都是一種不穩(wěn)定的實(shí)現(xiàn)細(xì)節(jié),而客戶代碼對(duì)于不穩(wěn)定實(shí)現(xiàn)細(xì)節(jié)的大面積依賴,會(huì)導(dǎo)致系統(tǒng)耦合度的急劇上升。從而讓系統(tǒng)在變化面前極其脆弱。

而為了提升內(nèi)聚度,降低耦合度,我們需要采取向著穩(wěn)定方向依賴的策略,從而得到:

類對(duì)數(shù)據(jù)和算法的封裝
類對(duì)數(shù)據(jù)和算法的封裝

盡管依賴范圍(數(shù)量)依然,但由于API是站在站在客戶真正需要的角度定義的更為穩(wěn)定的抽象接口(參見(jiàn)《我們?cè)撊绾味xAPI》),其穩(wěn)定度大大提升,從而降低了系統(tǒng)的耦合度。

更何況,很多時(shí)候,經(jīng)過(guò)封裝,甚至連依賴范圍也會(huì)隨之縮小。

TELL, DON'T ASK

Greg Willits在論文《OOP Principles in Action: Tell, Don't Ask》的開(kāi)篇就解釋了TDA的思想:

Tell, Don't Ask is a paradigm or style of object oriented programming which prefers that objectA tells objectB to do something, rather than asking ob- jectB about its state so that objectA can make a decision.

這種思想對(duì)于一直以面向過(guò)程方式思考的人是顛覆性的。

很多程序員,即便開(kāi)始使用C++JavaOO語(yǔ)言,也會(huì)很習(xí)慣的一上來(lái)就為每個(gè)數(shù)據(jù)成員都添加上SetterGetter接口。

當(dāng)然把數(shù)據(jù)全部都暴露出去,會(huì)讓程序員在系統(tǒng)各處使用時(shí)非常便利。但這種貪圖一時(shí)之爽的便利背后意味著對(duì)系統(tǒng)可維護(hù)性的長(zhǎng)期危害。

這種做法是典型的數(shù)據(jù)+算法的程序設(shè)計(jì)思路,完全沒(méi)有任何高內(nèi)聚,低耦合的基本素養(yǎng)。對(duì)于這樣的設(shè)計(jì),OO不僅不會(huì)給你帶來(lái)任何好處,反而增加了很多麻煩(更復(fù)雜的語(yǔ)法,類的原子不可分割性等等),這也是多年來(lái)OO在一部分圈子里承受惡名的重要原因之一。但這究竟是誰(shuí)的錯(cuò)?

可是,這并非意味著所有的Getter都是邪惡的。封裝的目的在于封裝變化。如果本來(lái)某種信息的獲取就是一種穩(wěn)定的本質(zhì)需要,而不是易于變化的實(shí)現(xiàn)細(xì)節(jié),那么這種Getter就是合理的。

例如,每一個(gè)Entity對(duì)象,都有一個(gè)身份證號(hào)Identity),通過(guò)它,可以從倉(cāng)庫(kù)(Repository)中對(duì)Entity進(jìn)行查找,因而一個(gè)Entity暴露一個(gè)getId()的接口是非常合理的。比如:

struct FooEntity
{
   unsigned int getId() const;
   // ...
};

但同時(shí),對(duì)于Id表示卻有必要進(jìn)行封裝,因?yàn)樗芸赡苁莻€(gè)不穩(wěn)定的實(shí)現(xiàn)細(xì)節(jié),今天或許是一個(gè)32位整數(shù),明天就會(huì)變?yōu)?code>64位,后天干脆變成了一個(gè)128位的字符串(你要是覺(jué)得這也是穩(wěn)定的,不妨想想我們身份證號(hào)的升位)。而當(dāng)變化沒(méi)有發(fā)生之前,你至少要先通過(guò)最簡(jiǎn)單的手段將這個(gè)信息進(jìn)行封裝(隱藏),比如:

typedef unsigned int FooId;

struct FooEntity
{
   FooId getId() const;
   // ...
};

這樣的做法,考慮其投入的成本,可獲得的收益,絕不屬于過(guò)度設(shè)計(jì)

最后,即便回到最壞的局面,Getter還是會(huì)比直接暴露數(shù)據(jù)略勝一籌。畢竟,Getter是個(gè)函數(shù),而函數(shù)比數(shù)據(jù)更具備彈性

避免重復(fù)

眾所周知,重復(fù)是軟件可維護(hù)性問(wèn)題的主要原因之一。

在一些以C語(yǔ)言為主開(kāi)發(fā)的大系統(tǒng)中,類型的重復(fù)定義,同一算法的重復(fù)實(shí)現(xiàn)是一個(gè)非常普遍的現(xiàn)象。這種重復(fù)很多時(shí)候并非故意為之,而是在缺乏封裝的情況下,必然導(dǎo)致的結(jié)果。

比如,在如下代碼中,結(jié)構(gòu)體Rectangle被傳遞給不同的函數(shù), 而每個(gè)函數(shù)都有基于Rectangle的計(jì)算。

struct Rectangle 
{ 
  int width; 
  int height; 
};
// 模塊的外部接口,rect 由其它模塊傳入 
void f1(Rectangle* rect){ 
  int area = rect->width * rect->height; 
  // ... 
} 

void f2(Rectangle* rect) 
{ 
  int perimeter = 2 * (rect->width + rect->height); 
  // ... 
}
 
void f3(Rectangle* rect) 
{ 
  // 和 f1 一樣的計(jì)算
  int area = rect->width * rect->height; 
  // ... 
} 

從中,我們能夠看到f1f3 有著相同的計(jì)算。而類似于這樣的重復(fù),在大型系統(tǒng)中非常普遍。

我們可以站在一個(gè)開(kāi)發(fā)者的角度,來(lái)思考為何會(huì)產(chǎn)生這樣的結(jié)果。

由于C的結(jié)構(gòu)體是不能有行為的,那么結(jié)構(gòu)體的所定義的數(shù)據(jù)操作它們的數(shù)據(jù)行為就是分離的 (缺乏內(nèi)聚性)。那么對(duì)一個(gè)開(kāi)發(fā)者而言,既然他可以輕松拿到所需數(shù)據(jù),他所需要的計(jì)算就可以親自完成;至于別人是否已經(jīng)實(shí)現(xiàn)過(guò)類似的代碼,他并不知道。難道要讓他去龐大的代碼庫(kù)中四處搜索?這還不如自己親自寫(xiě)更方便一些。

其造成的結(jié)果必然是:相同的算法就被多個(gè)開(kāi)發(fā)者重復(fù)實(shí)現(xiàn)。只有極其有心的開(kāi)發(fā)者才可能會(huì)盡力的降低一些這類的重復(fù)。當(dāng)然,這是以付出額外精力為代價(jià)的。

但一旦把這些行為都放在類中,把數(shù)據(jù)隱藏起來(lái),就可以最大程度的避免重復(fù)。

首先開(kāi)發(fā)者們失去了自由訪問(wèn)數(shù)據(jù)的權(quán)力。如果一個(gè)開(kāi)發(fā)者需要某種計(jì)算,數(shù)據(jù)訪問(wèn)權(quán)的喪失會(huì)讓他首先考慮的不是在類的外部親自實(shí)現(xiàn)某個(gè)算法,而是首先尋求復(fù)用別人的實(shí)現(xiàn)。

那別人是否曾經(jīng)實(shí)現(xiàn)過(guò)呢?無(wú)須到系統(tǒng)中漫無(wú)邊際的搜索,只需要看看這個(gè)類有沒(méi)有相應(yīng)函數(shù)即可(內(nèi)聚性是多么重要)。如果沒(méi)有,則添加一個(gè),首先滿足了自己,別人如果需要,當(dāng)然也可以復(fù)用。

所以,我們可以其修改為如下的樣子:

struct Rectangle 
{ 
  // ... 
  unsigned int getArea() const 
  { 
    return width * height; 
  }
   
  unsigned int getPerimeter() const 
  { 
    return 2 * (width + height); 
  } 
  
private:
  unsigned int width; 
  unsigned int height; 
};

有了這樣的定義之后,此類的用戶甚至都不用到類的定義處去查看接口。在IDE的幫助下,程序員可以迅速判斷自己所需的接口是否存在:

IDE的幫助
IDE的幫助

有了類的封裝,Rectangle的客戶將不再可能編寫(xiě)重復(fù)的算法:

void f1(const Rectangle& rect) 
{ 
  unsigned int area = rect.getArea(); 
  // ... 
} 

void f2(const Rectangle& rect) 
{ 
  unsigned int perimeter = rect.getPerimeter(); 
  // ... 
} 

void f3(const Rectangle& rect) 
{ 
  unsigned int area = rect.getArea(); 
  // ... 
}

IDE對(duì)于效率的影響

多年前我剛剛加入ThoughtWorks時(shí),有幸與PicoContainer的作者Jon Tirson一個(gè)團(tuán)隊(duì),并與他結(jié)對(duì)編程。第一次結(jié)對(duì),我就被他神一般的編碼速度所震驚:大手一抹,整片的Java代碼就出現(xiàn)在屏幕上。

震驚之余,我開(kāi)始仔細(xì)觀察他是如何做到的:除了他確實(shí)敲鍵盤(pán)的速度很快之外,他對(duì)IDE的應(yīng)用極其熟練:各種熱鍵,代碼模版用的滾瓜爛熟,并非常善于利用IDE的提示信息等等。

在靜態(tài)面向?qū)ο笳Z(yǔ)言里,class作為行為的載體,會(huì)非常方便于IDE給出程序員提示信息。這對(duì)于程序員的高效編碼極有幫助。(這也是我在進(jìn)行C或者Haskell編程時(shí),很多函數(shù)名都必須靠自己記憶,而不是靠IDE幫助,感覺(jué)特別不爽的原因)。

提高內(nèi)聚性

我們通過(guò)一個(gè)例子:Maybe問(wèn)題,來(lái)看看封裝如何有利于提高包括數(shù)據(jù)自身的內(nèi)聚性的。

每個(gè)CC++程序員都處理過(guò)空指針問(wèn)題。比如,我們?cè)谝粋€(gè)單向鏈表的典型實(shí)現(xiàn)中,我們會(huì)讓對(duì)象持有一個(gè)指向下一個(gè)對(duì)象的指針:

struct Object 
{ 
  void f(); 
  void g(); 
  Object* next; // 指向下一個(gè)節(jié)點(diǎn)的指針 
  // ... 
};

其中,next指針為空是一種合法的情況,它表示當(dāng)前對(duì)象是單向鏈表中的最后一個(gè)節(jié)點(diǎn)。所以,我們可能會(huì)編寫(xiě)如下的代碼:

void Object::f() 
{ 
  // ... 
  if(next != 0) 
  { 
    next->g(); 
  } 
  // ... 
}

所以,任何一個(gè)指針,事實(shí)上都包含了兩種語(yǔ)義:

  1. 指針是否有效(是否是一個(gè)空指針);
  2. 其二,如果有效,它的值是什么(指向哪個(gè)對(duì)象)。

指針天然就存在這樣的雙重語(yǔ)義,而其它類型則不然。但程序員們可以通過(guò)自定義的方式,為其它類型引入這種雙重語(yǔ)義。

比如,程序員們經(jīng)常選擇0xffffffff當(dāng)作uint32_t類型的“空指針”。比如:

const u32 INVALID_VALUE = 0xffffffff; 
// ... 

void f(const u32 value) 
{ 
  if(value != INVALID_VALUE) 
  { 
    // 使用 value 進(jìn)行進(jìn)一步的運(yùn)算 
    g(value * 5);
  }
  // ... 
}

但無(wú)論是空指針,還是自定義無(wú)效數(shù)值,都并非在所有場(chǎng)景下都適用。

比如, 系統(tǒng)并不想通過(guò)動(dòng)態(tài)分配內(nèi)存的方式來(lái)分配對(duì)象,所以,你無(wú)法使用空指針來(lái)區(qū)分?jǐn)?shù)據(jù)的有效性。而對(duì)于整數(shù)來(lái)說(shuō),如果其取值范圍內(nèi)的所有數(shù)值都是有效數(shù)值,你也不可能找個(gè)一個(gè)數(shù)值來(lái)代表無(wú)效數(shù)值

這種情況下,程序員們往往會(huì)選擇專門(mén)用一個(gè)bool型的標(biāo)志來(lái)標(biāo)示數(shù)據(jù)的有效性。比如:

struct Object 
{ 
  void f(); 
  
  // ...  
  Foo foo; // 不想動(dòng)態(tài)分配內(nèi)存 
  bool isFooEffective; 
  
  u32 value; // [0, 0xffffffff] 均為有效值
  bool isValueEffective;  // ... 
};

對(duì)于這類數(shù)據(jù)的操作模式往往是:


void Object::f() 
{ 
  int result = 0; 
  
  if(isValueEffective) 
  { 
    result += value;
  }
  
  if(isFooEffective) 
  { 
    result += foo.getValue();
  }
  
  // ... 
}

這種一分為二的方法,還原了事情的原貌:本來(lái)它們就是雙重語(yǔ)義。

但這種語(yǔ)義的分離卻帶來(lái)一個(gè)缺點(diǎn)。毫無(wú)疑問(wèn),這兩個(gè)語(yǔ)義是緊密關(guān)聯(lián)的,任何一個(gè)離開(kāi)另外一個(gè)都讓原有的語(yǔ)義不再完整。但從形式上,這種緊密的關(guān)聯(lián)性卻沒(méi)有得到任何體現(xiàn),只有通過(guò)仔細(xì)分析代碼,才可以確定這種關(guān)聯(lián)性。這無(wú)疑會(huì)傷害代碼的可理解性。

解決這個(gè)問(wèn)題的辦法,當(dāng)然是再將它們合在一起,形成一個(gè)完整的概念: Maybe

template <typename T> 
struct Maybe
{ 
  Maybe() 
    : effective(false) 
  {}
  
  Maybe(const T& data) 
    : data(data), effective(true)
  {}
   
  bool isEffective() const 
  { 
    return effective; 
  }
   
  T* operator->() 
  { 
    return effective ? &data : 0; 
  } 

  const T* operator->() const 
  { 
    return effective ? &data : 0; 
  }
   
  T& operator*() 
  { 
    return *(operator->()); 
  }
   
  const T& operator*() const 
  { 
    return *(operator->()); 
  }
   
  void clear() 
  { 
    effective = false; 
  }
   
  // ... 
private:
  T data; 
  bool effective; 
};

借助這個(gè)定義,之前的那些定義就可以改變?yōu)?

struct Object
{ 
  // ... 
  Maybe<Foo> foo;
  Maybe<u32> value;
  // ...
};

然后,對(duì)于Maybe類型的數(shù)據(jù)訪問(wèn)方式也改變?yōu)?

void Object::f()
{ 
  int result = 0; 
  if(value.isEffective())
  {
    result += (*value);
  }
  
  if(foo.isEffective()) 
  { 
     result += foo->getValue();
  }
  // ... 
}

如果你仔細(xì)觀察,就會(huì)發(fā)現(xiàn):通過(guò)Maybe這樣的定義,我們又重新恢復(fù)了空指針語(yǔ)義。每一個(gè)Maybe類型的數(shù)據(jù)都是一個(gè)指針,只有在有效的情況下,才可以對(duì)指針指向的內(nèi)容進(jìn)行訪問(wèn),否則,對(duì)其訪問(wèn)將會(huì)引起異常。

另外,這種實(shí)現(xiàn)方式,并沒(méi)有簡(jiǎn)化客戶代碼,它仍然需要使用if結(jié)構(gòu)來(lái)確定數(shù)據(jù)的有效性,然后再對(duì)數(shù)據(jù)進(jìn)行訪問(wèn),這和原有方式的復(fù)雜度是一樣的。但這種方式的關(guān)注點(diǎn)在于,通過(guò)將兩個(gè)分散的變量定義(數(shù)據(jù)和有效性標(biāo)志)合二為一,形成一個(gè)更明確的概念,以提高代碼的表現(xiàn)力,增強(qiáng)可理解性。

另外,在原有方式下,程序員需要考慮每一個(gè)標(biāo)志的命名,并要確保在邏輯判斷處引用了對(duì)應(yīng)的標(biāo)志;并且,當(dāng)需要?jiǎng)h除一個(gè)數(shù)據(jù)時(shí),也需要同時(shí)考慮刪除其對(duì)應(yīng)的標(biāo)志,這都給可維護(hù)性帶來(lái)更多代價(jià)。而Maybe則統(tǒng)一了這類問(wèn)題的操作方式,從而讓程序員在編寫(xiě),閱讀和維護(hù)這類代碼時(shí),免除了上述的麻煩。

Setter

一個(gè)類不管三七二十一,都直接提供對(duì)所有數(shù)據(jù)成員的Setter接口,很多時(shí)候,像Getter一樣,都是將不穩(wěn)定的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)進(jìn)行暴露的方式。

Setter問(wèn)題也并不都是邪惡的。為了說(shuō)明這個(gè)問(wèn)題,讓我們?cè)俅位氐?code>Rectangle的例子:

struct Rectangle 
{ 
  Rectangle
    ( unsigned int width
    , unsigned int height) 
    : width(width)
    , height(height)
  {} 

  unsigned int getArea() const 
  { return m_width * m_height; } 

  unsigned int getPerimeter() const 
  { return m_width * m_height; } 
  
private:
  unsigned int width; 
  unsigned int height; 
}; 

此類所構(gòu)造出來(lái)的對(duì)象,都是Immutable的:其狀態(tài)自從構(gòu)造后,就再也不可修改。

如果Immutability正是對(duì)Rectangle的要求,那么你絕不應(yīng)該擅自增加對(duì)于widthheightSetter接口。

但如果我們隨后從需求上確實(shí)要求能夠修改其長(zhǎng)度和寬度,那就不得不增加相應(yīng)的Setter

struct Rectangle 
{ 
  Rectangle
    ( unsigned int width
    , unsigned int height) 
    : width(width)
    , height(height)
  {} 

  void setWidth(unsigned int width) 
  { m_width = width; } 
  
  void setHeight (unsigned int height) 
  { m_height = height; } 

  // ...
private:
  unsigned int width; 
  unsigned int height; 
}; 

這樣的Setter在需求的驅(qū)動(dòng)下,并沒(méi)有什么危害性。最主要的原因在于,WidthHeight本來(lái)就是一個(gè)Rectangle的穩(wěn)定屬性。

另外,在本例中,構(gòu)造函數(shù)和Setter方法,看起來(lái)都是為了給成員變量賦值。它們似乎是可以相互替代的賦值手段,其差異不過(guò)是:構(gòu)造看起來(lái)要比Setter更簡(jiǎn)潔。

這是一種完全錯(cuò)誤的看法。兩者從任何一個(gè)角度看,都是完全不同的兩類事物。

首先,兩者的客戶不同:構(gòu)造函數(shù)面向的用戶是Creator,而其它成員函數(shù)的用戶則是類服務(wù)需要者,即類真正的Clients

構(gòu)造與服務(wù)
構(gòu)造與服務(wù)

盡管可以由單個(gè)類同時(shí)承擔(dān),但CreatorClients本質(zhì)上是兩種完全不同的角色。在需要分離不同變化方向時(shí)的時(shí)候,CreatorClient經(jīng)常由不同的類來(lái)扮演。

其次,當(dāng)一個(gè)類實(shí)例被構(gòu)造完成時(shí),就應(yīng)該能夠表現(xiàn)它所表現(xiàn)的對(duì)象。比如,當(dāng)Rectangle rect(3,4)執(zhí)行結(jié)束后,實(shí)例rect就應(yīng)該能夠代表一個(gè)寬為3,高為4Rectangle

而表達(dá)式Rectangle rect構(gòu)造出的rect其實(shí)什么都不是。它無(wú)法履行它的職責(zé),即無(wú)法表現(xiàn)一個(gè)合法的矩形。

所以,構(gòu)造函數(shù)的目的,是為了創(chuàng)造出一個(gè)合法的、能夠履行職責(zé)的、能夠代表它要表現(xiàn)的概念實(shí)例。

Setter函數(shù),則是提供給Client的一個(gè)狀態(tài)變更接口。它的存在,完全取決于客戶是否需要。從這個(gè)意義上,Setter和任何其它服務(wù)函數(shù)沒(méi)有任何差別。

所以,構(gòu)造和Setter并非二選一的關(guān)系,而是為不同目的存在、完全沒(méi)有任何關(guān)系的兩套接口。它們可以同時(shí)存在,也可以根據(jù)需要選擇其中任何一種。

Rectangle為例,通過(guò)構(gòu)造函數(shù)來(lái)得到合法的Rectangle實(shí)例是必須的。如果沒(méi)有任何狀態(tài)變更的需要,那么在構(gòu)造結(jié)束后,我們就得到了一個(gè)值對(duì)象Value Object)。而一個(gè)值對(duì)象的成員函數(shù),除了構(gòu)造和析構(gòu),都應(yīng)該有const修飾。構(gòu)造時(shí)賦值是設(shè)置值對(duì)象狀態(tài)的唯一時(shí)機(jī)。

如果。隨后Client要求一個(gè)狀態(tài)變更接口,無(wú)論是否是Setter,只要可能改變實(shí)例的狀態(tài),那么它就是一個(gè)普通對(duì)象

總結(jié)

面向?qū)ο蟮闹饕康氖菫榱?strong>模塊化。而封裝作為OO的三大特征之一,其主要目的,是在模塊化的過(guò)程中通過(guò)信息隱藏,封裝變化,從而提高系統(tǒng)應(yīng)對(duì)變化的能力。

本文通過(guò)幾個(gè)例子,從不同側(cè)面講述了關(guān)于封裝的作用和方法。而關(guān)于如何做好封裝,總是可以回到高內(nèi)聚低耦合的角度來(lái)思考,通過(guò)正交策略來(lái)指導(dǎo)。

相關(guān)鏈接

其它與封裝有關(guān)的話題:《容器與封裝》

關(guān)于變化:《第一顆子彈》

關(guān)于OO的目的: 《正交設(shè)計(jì): OO與SOLID》

關(guān)于如何避免過(guò)度設(shè)計(jì):《簡(jiǎn)單設(shè)計(jì)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,521評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,493評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,603評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,813評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,953評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,209評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,033評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,693評(píng)論 25 708
  • 1 Why Class (這篇文章是一篇新手級(jí)的文章,請(qǐng)高手繞道)最初人們編程的時(shí)候都是用過(guò)程在編程。然而隨著使用...
    老喬理查德閱讀 3,220評(píng)論 0 2
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,197評(píng)論 30 471
  • 作為從農(nóng)村向城市過(guò)渡的一代人,這代人注定是艱苦的,悲哀的,偉大的!
    小小書(shū)童吶閱讀 374評(píng)論 0 0
  • 今日農(nóng)歷初一,公車(chē)路過(guò)小街城郊,有一些老少在道路邊燒著紙錢(qián),賑濟(jì)遺孤。我于此是不反感的。逢年過(guò)節(jié)燒一燒,表達(dá)一下哀...
    化濁閱讀 214評(píng)論 1 1