---
# System prepended metadata

title: Clean Code 6 - 物件及資料結構

---

# 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)



 














