# Clean Code 6 - 物件及資料結構
## 資料抽象化
具體的座標點
```ruby
class Point
attr_accessor :x, :y
end
```
抽象的座標點
```ruby
class Point
attr_reader :x, :y, :r, :theta
def set_cartesian(x, y)
# ...
end
def setPolar(r, theta)
# ...
end
end
```
差異
| 具體的座標點 | 抽象的座標點 |
| ------------------------------------------------------------ | ------------------------------------------------- |
| 從介面就可以看出資料結構 | 從介面看不出資料結構 |
| 可單獨設定 x 或 y (這邊的 point 可以看做是一個值,x 跟 y 應該要一起設定較好) | 設定座標點時,限制一定要同時設定 x, y 或 r, theta |
| 暴露了程式的實現過程 | 隱藏實作 |
## 資料/物件的反對稱性
| 物件 | 資料 |
| ------------------------------------------------ | ------------------------------------------------------------ |
| 資料被隱藏起來,需要透過物件提供的介面去操作資料 | 將資料暴露在外,可以直接讀取或寫入資料。譬如 Hash 或是上面的「具體的座標點」 |
| 包含行為 | 不包含行為 |
**Procedural Shape**
```java
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();
}
}
```
**Polymorphic Shape**
```java
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;
}
}
```
| | Procedural Shape | Polymorphic Shape |
| --------------------------------- | ------------------------------------------ | ------------------------------- |
| 新增 function,譬如計算 perimeter | 只需在 Geometry 裡面新增一個 function 即可 | 需要在所有類別新增這個 function |
| 新增類別,譬如 Triangle | Geometry 裡面所有的 function 都需要修改 | 新增一個類別即可 |
| | 方便新增函式 | 方便新增類別 |
## The Law of Demeter
the Law of Demeter says that a method `f` of a class `C` should only call the methods of these:
- `C`
- An object created by `f`
- An object passed as an argument to `f`
- An object held in an instance variable of `C`
```ruby
class User
def function_a(company)
# ok
function_b
# ok
service = MailService.new
service.deliver
# ok
company.do_somthing
# ok
@instance_variable.do_something
# ok if get_options returns data
# not ok if get_options returns object
company.get_options.get_scratch_dir
end
def function_b
# ...
end
end
```
## 隱藏結構
火車事故章節的程式碼
```java
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
```
當有程式違反 LOD 時,應該要停下來思考,為什麼這邊需要知道這麼多資訊?
當我們再往下看程式,發現拿絕對路徑是為了「要在此目錄下產生一個給定名稱的檔案」
```
String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);
```
因此,我們可以把「產生檔案」這件事情丟給 ctxt 做(因為 ctxt 知道絕對路徑是什麼)
就可以遵循 LOD,ctxt 也可以隱藏內部結構
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName)
## 資料傳輸物件 (Data Transfer Object, DTO)
一個類別裡面只有公用變數,沒有任何函式。
ruby 的 Struct?
## 活動記錄 (Active Records)
通常擁有 save 與 find 等方法。
通常是由資料庫或其他資料來源轉換而來。
不幸的是,我們常發現,開發者會試著將這類的資料結構看做是物件,然後加入處理商業法則的方法(咦!!?
## Value Object (補充)
[Rails value object design pattern (longliveruby.com)](https://longliveruby.com/articles/rails-value-object-design-pattern)