<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Clean Code - 對象和數(shù)據(jù)結(jié)構(gòu)

          共 7202字,需瀏覽 15分鐘

           ·

          2022-03-26 14:33

          1、數(shù)據(jù)抽象

          不曝露數(shù)據(jù)細節(jié),更愿意以抽象形態(tài)表述數(shù)據(jù)。

          代碼 1

          public?class?Point{
          ????public?double?x;
          ????public?double?y;
          }

          代碼 2

          public?interface?Point{
          ????double?getX();
          ????double?getY();
          ????void?setCartesian(double?x,?double?y);
          ????
          ????double?getR();
          ????double?getTheta();
          ????void?setPolar(double?r,?double?theta);
          }

          上面兩段代碼都表示笛卡爾兒平面上的一個點,你覺得哪個代碼更好些?

          答案是代碼 2,為什么代碼 2 更好一些,原因有以下兩個方面。

          首先代碼 1 沒有封裝。
          代碼 1 中的 x 和 y 是完全暴露的,任何人都可以直接訪問和設(shè)置新的值。
          代碼 2 中加了一層封裝,你只能通過get方法獲取坐標值和set方法設(shè)置原子坐標值。

          其次是具象與抽象。
          代碼 1 是具象的一個點,只能表示在直角坐標系中的一個點。
          代碼 2 是抽象的一個點,既可以表示直角坐標系中的一個點,也可表示極坐標系中的一個點。

          2、數(shù)據(jù)、對象的反對稱性(過程式代碼和面向?qū)ο蟠a的反對稱性)

          我們用一個例子來說明這個規(guī)則的含義。

          現(xiàn)在我們有一個需求,需要繪制三種幾何圖形,分別是正方形、長方形和圓形,并計算每個圖形的面積。

          代碼 1 是過程式代碼,每個形狀類都是簡單的數(shù)據(jù)結(jié)構(gòu),只負責存儲數(shù)據(jù),不具備行為。具體的計算行為放在了 Geometry 類中。

          代碼 1

          public?class?Square{
          ????public?Point?topLeft;
          ????public?double?side;
          }

          public?class?Rectangle{
          ????public?Point?topLeft;
          ????public?double?width;
          ????public?double?height;
          }

          public?class?Circle{
          ????public?Point?center;
          ????public?double?radius;
          }

          public?class?Geometry{
          ????public?final?double?PI?=?3.14159265358;
          ????
          ????public?double?area(Object?shape)?throws?NoSuchShapeException{
          ????????if(shape?instanceof?Square){
          ????????????Square?square?=?(Square)shape;
          ????????????return?square.side?*?square.side;
          ????????}?else?if?(shape?instanceof?Rectangle){
          ????????????Rectangle?rec?=?(Rectangle)shape;
          ????????????return?rec.width?*?rec.hight;
          ????????}?else?if?(shape?instanceof?Circle){
          ????????????Circle?cir?=?(Circle)shape;
          ????????????return?PI?*?cir.radius?*?cir.radius;
          ????????}
          ????????throw?new?NoSuchShapeException();
          ????}
          }

          代碼 2 是面向?qū)ο蟠a。每個形狀類是一個對象,不僅存儲數(shù)據(jù),還包含其行為。這三個形狀類都實現(xiàn) Shape 接口,每種形狀類都有各自的計算面積的方法。

          代碼 2

          public?interface?Shape{
          ????double?area();
          }

          public?class?Square?implements?Shape{
          ????private?Point?topLeft;
          ????private?double?side;
          ????
          ????public?double?area(){
          ????????return?side*side;
          ????}
          }

          public?class?Rectangle?implements?Shape{
          ????private?Point?topLeft;
          ????private?double?width;
          ????private?double?height;
          ????
          ????public?double?area(){
          ????????return?width?*?height;
          ????}
          }

          public?class?Circle?implements?Shape{
          ????private?static?final?double?PI?=?3.14159265358;?
          ????
          ????private?Point?topLeft;
          ????private?double?radius;
          ????
          ????public?double?area{
          ????????return?PI?*?radius?*?radius;
          ????}
          }

          有人可能會說,既然 Java 是一門面向?qū)ο箝_發(fā)語言,那肯定代碼 2 要優(yōu)于代碼 1,我們在平常的開發(fā)過程中,要多寫代碼 2 這種面向?qū)ο蟠a,少寫代碼 1 這種過程式代碼。

          但是!?。〔⒉皇沁@樣的!實際情況是,在某種情況下,過程式代碼優(yōu)于面向?qū)ο蟠a,在另一種情況下,面向?qū)ο蟠a優(yōu)于過程式代碼。我們是選擇過程式代碼還是面向?qū)ο蟠a,需要根據(jù)具體情況具體分析。

          舉個栗子!現(xiàn)在增加了一個需求,計算這三種圖形的周長。如果是代碼 1 的話,只需要給 Geometry 類添加一個 perimeter() 函數(shù),三個形狀類不會受到任何影響。如果是代碼 2 的話,需要給每個形狀類都添加一個 perimeter() 函數(shù)。

          現(xiàn)在增加另外一個需求,需要添加一個新形狀。如果是代碼 2 的話,只需要添加一個新的形狀類,既有的形狀類不會受到任何影響。如果是代碼 1 的話,Geometry 類的每個函數(shù)都要修改,增加一個 if else 分支。

          所以,我們得出以下結(jié)論:
          過程式代碼便于在不改動既有數(shù)據(jù)結(jié)構(gòu)的前提下添加新的函數(shù),而面向?qū)ο蟠a便于在不改動既有函數(shù)的前提下添加新類。

          所以,對于面向?qū)ο筝^難的事,對于過程式代碼卻較容易,反之亦然!這就是過程式代碼和面向?qū)ο蟠a的反對稱性!由于數(shù)據(jù)結(jié)構(gòu)是過程式代碼的基本單元,對象是面向?qū)ο蟠a的基本單元,所以,這也可以說是數(shù)據(jù)和對象的反對稱性。

          在任何復雜系統(tǒng),都會有需要添加新數(shù)據(jù)類型而不是新函數(shù)的時候,這時,對象和面向?qū)ο缶捅容^適合。另一方面,也會有想要添加新函數(shù)而不是數(shù)據(jù)類型的時候,在這種情況下,數(shù)據(jù)結(jié)構(gòu)和過程式代碼更合適。

          3、得墨忒耳定律

          著名的得墨忒耳定律認為,模塊不應該了解它所操作對象的內(nèi)部情形。

          即類? C? 的方法? f? 只應該調(diào)用以下對象的方法:

          • C
          public?class?C{
          ??public?void?f1(){
          ????f2();
          ??}
          ??
          ??public?void?f2(){
          ??
          ??}
          }
          • 由? f? 創(chuàng)建的對象
          public?class?C1{
          ??public?void?f(){
          ????C2?c2?=?new?C2();
          ????c2.f();
          ??}
          }

          public?class?C2{
          ??public?void?f(){
          ????/*...*/
          ??}
          }
          • 作為參數(shù)傳給? f? 的對象
          public?class?C1{
          ??public?void?f(C2?c2){
          ????c2.f();
          ??}
          }

          public?class?C2{
          ??public?void?f(){
          ????/*...*/
          ??}
          }
          • 由? C? 的實體變量持有的對象
          public?class?C1{
          ??private?List?names?=?new?ArrayList();
          ??
          ??public?void?f(String?name){
          ????names.add(name);
          ??}
          }

          方法不應調(diào)用由任何函數(shù)返回的對象的方法。

          下列代碼違反了得墨忒耳定律,因為它調(diào)用了 getOptions() 函數(shù)返回的對象的 getScratchDir() 函數(shù),又調(diào)用了 getScratchDir() 函數(shù)返回的對象的 getAbsolutePath() 函數(shù)。

          final?String?outputDir?=?ctxt.getOptions().getScartchDir().getAbsolutePath();

          這類代碼常被稱作火車失事,因為涉及到多個函數(shù)的級聯(lián)調(diào)用,一旦出了問題,不知問題出在哪。最好做類似如下的切分:

          Options?opts?=?ctxt.getOptions();
          File?scratchDir?=?opts.getScratchDir();
          final?String?outputDir?=?scratchDir.getAbsolutePath();

          優(yōu)化后的代碼是否違反了得墨忒耳定律呢?

          這要看 ctxt、Options、ScratchDir 是對象還是數(shù)據(jù)結(jié)構(gòu),如果是對象,就違反了得墨忒耳定律,因為模塊知道它要操作的所有對象的內(nèi)部情形(為什么這么說呢?看優(yōu)化后的代碼,模塊知道?ctxt 對象包含有多個選項,每個選項都有一個臨時目錄,而每個臨時目錄都有一個絕對路徑。模塊對于它要操作的這三個對象,每個對象內(nèi)部有啥,了解地清清楚楚)。

          如果是數(shù)據(jù)結(jié)構(gòu),由于數(shù)據(jù)結(jié)構(gòu)只包含數(shù)據(jù),沒有什么行為,則他們自然會暴露其內(nèi)部數(shù)據(jù)結(jié)構(gòu),得墨忒耳定律也就失效了。

          如果是數(shù)據(jù)結(jié)構(gòu),就應該這樣寫代碼:

          final?String?outputDir?=?ctxt.options.scratchDir.absolutePath;

          如果是對象,這段代碼違反了得墨忒耳定律,那如何優(yōu)化讓其不違反這個定律呢?我們可以將這段代碼的邏輯全部抽取到 ctxt 的某個函數(shù)中,此時 ctxt 隱藏了內(nèi)部結(jié)構(gòu),防止當前函數(shù)因瀏覽它不該知道的對象而違反得墨忒耳定律

          BufferedOutputStream?bos?=?ctxt.createScratchFileStream(classFileName);

          4、數(shù)據(jù)傳送對象

          DTO(數(shù)據(jù)傳送對象)是一種只有公共變量,沒有函數(shù)(這個函數(shù)是指除 get、set 之外的函數(shù))的類。常見的數(shù)據(jù)傳送對象還有 Bean,這種結(jié)構(gòu)有賦值器和取值器操作私有變量。

          見下面的示例 1 和示例 2 ,二者都是 DTO,區(qū)別就是示例 1 只有數(shù)據(jù),由于數(shù)據(jù)權(quán)限是 public ,我們可以直接讀取數(shù)據(jù)或者給數(shù)據(jù)賦值,示例 2 使用 private 隱藏數(shù)據(jù),然后使用取值器 get 和賦值器 set 操作這些私有變量。

          示例 1

          public?class?Address{
          ??public?String?street;
          ??public?String?city;
          ??public?String?state;
          ??public?String?province;
          ??
          ??public?Address(String?street,?String?city,?String?state,?String?province){
          ????this.street?=?street;
          ????this.city?=?city;
          ????this.state?=?state;
          ????this.province?=?province;
          ??}
          }

          示例 2

          public?class?Address{
          ????private?String?street;
          ????private?String?city;
          ????private?String?state;
          ????private?String?province;
          ????
          ????public?Address(String?street,?String?city,?String?state,?String?province){
          ????????this.street?=?street;
          ????????this.city?=?city;
          ????????this.state?=?state;
          ????????this.province?=?province;
          ????}
          ????
          ????public?void?setStreet(String?street){
          ????????this.street?=?street;
          ????}
          ????
          ????public?String?getStreet(){
          ????????return?stree;
          ????}
          ????
          ????public?void?setCity(String?city){
          ????????this.city?=?city;
          ????}
          ????
          ????public?String?getCity(){
          ????????return?city;
          ????}
          ????
          ????public?void?setState(String?state){
          ????????this.state?=?state;
          ????}
          ????
          ????public?String?getState(){
          ????????return?state;
          ????}
          ????
          ????public?void?setProvince(String?province){
          ????????this.province?=?province;
          ????}
          ????
          ????public?String?getProvince(){
          ????????return?province;
          ????}
          }

          DTO 是非常有用的結(jié)構(gòu),尤其是在與數(shù)據(jù)庫通信,或解析套接字傳遞信息之類的場景之中。

          數(shù)據(jù)傳送對象應該是簡單的數(shù)據(jù)結(jié)構(gòu),不應該包含業(yè)務邏輯。如果對象有較多需要處理的業(yè)務邏輯,應當新建類來包含業(yè)務邏輯、隱藏內(nèi)部數(shù)據(jù)進行處理。

          舉個栗子來解釋一下,對于 Address 我們有查找地址 find() 和保存地址 save() 的需求,如果把這兩個業(yè)務邏輯寫入 Address 類中,Address 就不能說是一個數(shù)據(jù)傳送對象了。

          反例

          public?class?Address{
          ????private?String?street;
          ????private?String?city;
          ????private?String?state;
          ????private?String?province;
          ????
          ????public?Address(String?street,?String?city,?String?state,?String?province){
          ????????this.street?=?street;
          ????????this.city?=?city;
          ????????this.state?=?state;
          ????????this.province?=?province;
          ????}
          ????
          ????//?所有屬性的?set、get?方法
          ????
          ????public?Address?find(){
          ????/*...*/
          ????return?address;
          ??}
          ??
          ??public?void?set(String?street,?String?city,?String?state,?String?province){
          ????Address?address?=?new?Address();
          ????address.setStreet(street);
          ????address.setCity(city);
          ????address.setState(state);
          ????address.setProvince(province);
          ??}
          }

          想要保證 Address 是一個數(shù)據(jù)傳送對象,那么這兩個業(yè)務邏輯就不應該寫到 Address 類中,我們可以這樣操作,Adderss 類依舊保持上述示例 2 的樣子,然后創(chuàng)建一個新的類,在這個新的類中編寫這兩個業(yè)務邏輯。

          正例

          public?class?AddressOperator{
          ??public?Address?find(){
          ????/*...*/
          ????return?address;
          ??}
          ??
          ??public?void?set(String?street,?String?city,?String?state,?String?province){
          ????Address?address?=?new?Address();
          ????address.setStreet(street);
          ????address.setCity(city);
          ????address.setState(state);
          ????address.setProvince(province);
          ??}
          }

          5、小結(jié)

          對象曝露行為,隱藏數(shù)據(jù)。便于添加新對象類型而無需修改既有行為,同時也難以在既有對象中添加新的行為。
          數(shù)據(jù)結(jié)構(gòu)曝露數(shù)據(jù),沒有明顯的行為,便于向既有數(shù)據(jù)結(jié)構(gòu)添加新的行為,同時也難以向既有函數(shù)添加新的數(shù)據(jù)結(jié)構(gòu)。

          在任何系統(tǒng)中,我們有時會希望能夠靈活地添加新數(shù)據(jù)類型,所以更喜歡在這部分使用對象和面向?qū)ο蟠a。另外一些時候,我們希望能靈活地添加新行為,這時我們更喜歡使用數(shù)據(jù)結(jié)構(gòu)和過程式代碼。優(yōu)秀的軟件開發(fā)者能夠根據(jù)手邊工作的性質(zhì)靈活地選擇其中一種手段。


          瀏覽 98
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天拍,天天射,天天撸 | 欧美天天澡天天爽日日a | 色婷婷视频一区二区 | 看色婷婷免费视频 | 豆花无码成人免费视频 |