# 第六章 物件及資料結構 ###### tags: `Tag(Clean Code)` [TOC] ## 大綱 使用私有 (private) 變數的理由是「不希望任何人依賴這些變數」,但是有許多工程師會自動為它們==加上 getter 與 setter,讓私有變數如同公用 (public) 變數一般==。 探討資料結構與物件的不同之處,經常會在寫程式時用到這兩種技巧,但是它們的意義是不一樣的,為了方便管理與維護,必須時時刻刻在意究竟現在寫的是資料結構還是物件。 最後,德摩特爾法則是一種撰寫程式的規範,我們不應該知道太多物件內的事情,否則就會破壞物件的封裝,讓被使用的物件難以被維護。 ### 資料抽象化 * 具體: 資料結構,這暴露了程式的實踐過程,這是一個直角座標系。 ``` public class Point { public double x; public double y; } ``` * 抽象: 將實現的過程隱藏,不只是加上一層函式的介面而已,確切來說這是一種==抽象化的過程==,讓使用者在不需要知道實現的過程狀態下,還能夠操作資料的本質。 ``` public class Point { double getX(); double getY(); void setCartestion(double x, double y); double getR(); double getTheta(); void setPolar(double r, double theta); } ``` --- #### 舉例: * 具體化的交通工具類別 ``` FuleTankCapacityInGallons() { double getGallonsOfGasoline(); } ``` * 抽象化的交通類別 ``` public interface Vehicle { double getPercentFuelRemaining(); } ``` 兩種差別,最佳的方式使用==抽象化方式讓資料的細節隱藏==,命名表達資料。這並不是透過介面及讀取、設定函式。 嚴謹做法是「資料抽象概念」的方式。 最糟糕做法是get和set方式而已。 ### 資料、物件的反對稱性 * 資料結構與物件的差別 解釋資料結構 (Data Structure) 與物件 (Object) 為何不同,而且它們是反對稱性的存在。 * 物件: 物件將它們的資料在抽象層後方隱藏起來,然後將操作這些資料的函式暴露在外。 * 資料結構: 將資料暴露在外且沒有提供有意義的函式,可以直接對資料讀取與寫入。例如:list、dict、set 等 ### 舉例 **資料結構寫法** Geometry類別操控了三種圖形類別,這三種類別是簡單的資料結構 所以的圖形操作行為都定義在Geometry類別裡面。 但如果在==新增一個圖形類別,就必須改變Geometry裡所有函式==。 ``` public class Square { public Point topLeft; public double side; } public class Rectangle { public Point topLeft; public double height; public double width; } public class Circle { public Point center; public double radius; } ``` ``` public class Geometry { public final double PI = 3.141592653589793 ; public double area (Object shape) throws NoSuchShapeException { if (shape instanceof Square) { Square s = (Square) shape; return s.side * s.side; } else if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; return r. height * r. width; } else if (shape instanceof Circle) { Circle c = (Circle) shape; return PI * c. radius * c. radius; } } throw new NoSuchShapeException(); } ``` **物件導向寫法** ``` 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 height; private double width; public double area(){ return height * width; } } public class Circle implements Shape{ private Point center; private double radius; public final double PI = 3.141592653589793 ; public double area(){ return PI * radius * radius; } } ``` ### 總結 * 物件導向: 容易新增一個新的==類別==,而不用變動已有的函式。 ==相反==的新增函式,就必須改變所有的類別。 * 資料結構(結構化): 容易新增一個新的==函式==,不需要變動已有的資料結構 ==相反==的新增資料結構,就必須改變所以函式。 --- ### 德摩特爾法則 (The Law of Demeter) [維基百科說明](https://en.wikipedia.org/wiki/Law_of_Demeter) 模組不應該知道關於它所操作物件的內部運作,一個物件不應該透過存取者暴露其內部結構。 ### 舉例 以下違反了德摩特爾法則 因為在getOptions()的回傳物件上,呼叫了getScratchDir()。 還有在getScratchDir()的回傳物件上,呼叫了getAbsolutePath()。 ``` final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); ``` 因為在回傳物件上又呼叫了其他方法,因為這個模組知道==ctxt物件含有選項(options),而選項又包含了暫時目錄(ScratchDir),暫時目錄又包含了絕對路徑(AbsolutePath),這裡有著太多的資訊讓一個函式事先知道了==。 如果ctxt、Options、ScratchDir是物件則它的內部應該被隱藏起來,而非暴露在外。 如果它們是資料結構,那在本質上必然會揭露內部的結構,所以法則在這情況並不適用。 ### 資料傳輸物件(Data Transfer Object, DTO) 最佳的資料結構形式,是一個類別裡只有公用變數,沒有任何函式。這種資料結構有時被稱為資料傳輸物件或DTO 解析電文資料轉換成可以讀取和設定的物件。 ``` public class Bean_Banking_APP1001_1 extends Bean_Base { /** * 查詢日期時間 */ public String datetime; /** * 截至目前之可用餘額 */ public String bal; public String getDatetime() { return datetime; } public void setDatetime(String datetime) { this.datetime = datetime; } public String getBal() { return bal; } public void setBal(String bal) { this.bal = bal; } } ``` --- 讓每件事情都是一個物件是一個神話。某些時候,你真的只想使用簡單的資料結構,並透過結構化的程式碼來操作這些資料結構。 而德摩特爾法則是一種撰寫程式的規範,我們不應該知道太多物件內的事情,否則就會破壞物件的封裝,讓被使用的物件難以被維護。